1. GraphQL란?

공식 홈페이지에 따르면 GraphQL은 API를 위한 쿼리 언어(Query Language, QL)입니다.
SQL이 데이터베이스 시스템에서 데이터를 처리하는 역할을 한다면, GraphQL은 클라이언트와 서버 사이에서 데이터를 처리하는 용도로 사용됩니다.

2. GraphQL vs REST API

REST API와 비교했을 때 GraphQL만의 두드러지는 특징은 다음과 같습니다.

1. One Endpoint

REST API는 요청 형식(GET, POST, PUT, DELETE)이나 용도에 따라서 여러 개의 엔드포인트를 갖는 반면, GraphQL은 하나의 엔드포인트에서 POST로 모든 요청을 처리합니다.

id가 1인 직원의 정보가 필요한 경우

// GraphQL request
query {
  employee(id: 1) {
      name
      gender
      email
      ...
      address
  }
}

// GraphQL response
{
  "data": {
    "employee": {
        "name": "Hong Gildong",
        "gender": "male",
        "email": "gildong@ggg.com",
        ...
        "address": "Guro-gu, Seoul"
    }
  }
}



id가 1인 팀의 정보가 필요한 경우

// GraphQL request
query {
  team(id: 1) {
      name
      leader
  }
}

// GraphQL response
{
  "data": {
    "team": {
        "name": "Alpha",
        "leader": "Kim Cheolsoo",
    }
  }
}



2. Overfetching

REST API는 요청하여 응답 받은 정보에 필요하지 않은 데이터가 있는 경우가 종종 있습니다.
이때 필요없는 데이터를 전송하기 위해 네트워크 낭비가 발생합니다.
하지만 GraphQL를 이용하면 필요한 정보들만 요청하고 그에 해당하는 데이터만 응답 받을 수 있습니다.
예를 들어, 직원들의 주소를 제외하고 이름과 성별만 요청해서 응답 받을 수 있습니다.

id가 1인 직원의 이름, 성별이 필요한 경우

// GraphQL request
query {
  employee(id: 1) {
      name
      gender
  }
}

// GraphQL response
{
  "data": {
    "employee": {
        "name": "Hong Gildong",
        "gender": "male"
    }
  }
}



id가 1인 직원의 이름, 이메일, 주소가 필요한 경우

// GraphQL request
query {
  employee(id: 1) {
      name
      email
      address
  }
}

// GraphQL response
{
  "data": {
    "employee": {
        "name": "Hong Gildong",
        "email": "gildong@ggg.com",
        "address": "Guro-gu, Seoul"
    }
  }
}



3. Underfetching

REST API는 필요한 모든 데이터를 하나의 엔드포인트로 가져오지 못하기 때문에 여러 번의 API를 호출해야 하는 경우가 생깁니다. 즉, 요청 횟수가 증가하게 됩니다. 이와 달리, GraphQL은 원하는 정보를 한 번에 응답 받을 수 있습니다. 예를 들어, 팀 정보와 직원 정보를 하나의 엔드포인트로 가져올 수 있습니다.

id가 1인 팀에 소속된 직원들의 이름이 필요한 경우

// GraphQL request
query {
  team(id: 1) {
      name
      gender
      employees {
          name
      }
  }
}

// GraphQL response
{
  "data": {
    "team": {
        "name": "Alpha",
        "leader": "Kim Cheolsoo",
        "employees: [
              {
                  name: "Lee Suji"
              },
              {
                  name: "Park Yeonghee"
              }
        ]
    }
  }
}



3. Back - Spring for GraphQL

Spring for GraphQL은 'GraphQL Java' 팀과 'Spring Engineering' 팀이 공동으로 개발했으며, 'GraphQL Java' 팀이 개발한 graphql-java의 후속 프로젝트입니다.
Spring for GraphQL은 Spring과 GraphQL을 사용하는 모든 프로젝트의 기반이 되는 것을 목표로 하고 있으며, 다른 라이브러리를 사용할 때 해야하는 별도의 설정을 하지 않고 기존 MVC 개발하는 방식대로 개발할 수 있습니다.



src/main/resources/graphql 경로에 schema.graphqls 파일을 만들고 타입을 정의합니다.

""" Query, Mutation 에서 사용될 Employee 타입 """
type Employee {
    id: ID!
    name: String!
    gender: String
    email: String
    address: String
}

""" Employee를 조회하는 Query 타입 """
type Query {
    getEmployee(id: ID!): Employee
    getEmployees: [Employee]
}

""" Employee를 등록, 수정, 삭제하는 Mutation 타입 """
type Mutation {
    saveEmployee(employee: Employee!): Employee
}




schema.graphqls 파일에서 정의한 Employee 타입에 해당하는 Employee 클래스를 만듭니다.

public class Employee {
    private final String id;
    private final String name;
    private final String gender;
    private final String email;
    private final String address;

    public Person(String id, String name, String gender, String email, String address) {
        this.id = id;
        this.name = name;
        this.gender = gender;
        this.email = email;
        this.address = address;
    }

    public String getId() {
        return id;
    }
    
    public String getName() {
        return name;
    }
    
    public String getGender() {
        return gender; 
    }
    
    public String getEmail() {
        return email; 
    }
    
    public String getAddress() {
        return address; 
    }
}




Repository를 만듭니다.

@Repository
public class EmployeeRepository {

    private List<Employee> employees = new ArrayList<>();

    public Employee getEmployee(@Argument String id) {
        return employees.stream()
                .filter(employee -> employee.getId().equals(id))
                .findFirst()
                .orElseThrow(() -> new RuntimeException("Employee not found"));
    }

    public List<Employee> getEmployees() {
        return employees;
    }

    public Employee save(Employee employee) {
        employees.add(employee);
        return employee;
    }
    
    @PostConstruct
    private void init() {
        employees.add(new Employee("1", "Josh", "Male", "josh@gggg.com", "Guro-gu, Seoul"));
        employees.add(new Employee("2", "Mark", "Male", "mark@gggg.com", "Geumcheon-gu, Seoul"));
        employees.add(new Employee("3", "Greg", "Female", "greg@gggg.com", "Guro-gu, Seoul"));
    }
}




Controller를 만들고 schema.graphqls 파일에서 정의한 것과 같은 이름을 갖는 메소드를 만듭니다.

@RequiredArgsConstructor
@Controller
public class EmployeeController {

    private final EmployeeRepository employeeRepository;

    /**
     * @QueryMapping은 @SchemaMapping(typeName = "Query")를 줄인 어노테이션입니다.
     */
    @QueryMapping(value = "getEmployee") // .graphqls 파일에서 정의한 schema와 맵핑이 되어야 합니다. 생략하면 메소드 이름으로 초기화됩니다.
    public Employee getEmployee(@Argument String id) { // @Argument는 파라미터를 받을 때 사용하는 어노테이션입니다.
        return employeeRepository.getEmployee(id);
    }

    /**
     * @QueryMapping은 @SchemaMapping(typeName = "Query")를 줄인 어노테이션입니다.
     */
    @QueryMapping 
    public List<Employee> getEmployees() {
        return employeeRepository.getEmployees();
    }

    /**
     * @MutationMapping은 @SchemaMapping(typeName = "Mutation")를 줄인 어노테이션입니다.
     */
    @MutationMapping
    public Employee saveEmployee(@Argument Employee employee) { // @Argument는 파라미터를 받을 때 사용하는 어노테이션입니다.
        return employeeRepository.save(employee);
    }
}

4. Client - Apollo Client

Apollo Client는 React와 Typescript를 지원하며, Javascript의 GraphQL Client 라이브러리 중 가장 널리 쓰이고 있습니다. App.tsx 파일에서 ApolloProvider로 랩핑을 하고 사용할 ApolloClient를 주입합니다.

import React from 'react';
import {ApolloClient, ApolloProvider, InMemoryCache} from "@apollo/client";
import {Employee} from "./components";

const client = new ApolloClient({
    uri: 'http://localhost:8080/graphql',
    cache: new InMemoryCache(),
});

function App() {
    //

    return (
        <div className="App">
            <ApolloProvider client={client}>
                <Employee />
            </ApolloProvider>
        </div>
    );
}

export default App;




useQuery 와 useMutation 을 사용해서 Employee 를 조회, 등록, 수정, 삭제를 합니다.

import React from 'react';
import { useQuery } from '@apollo/react-hooks';
import gql from 'graphql-tag';

// Employee 인터페이스입니다.
interface Employee {
    id: string;
    name: string;
    gender: string;
    email: string;
    address: string;
}

// getEmployee 쿼리에서 사용할 파라미터입니다.
interface EmployeeVars {
    id: string;
}

// getEmployee 쿼리 입니다.
const GET_EMPLOYEE = gql`
  query GetEmployee($id: ID!) {
    getEmployee(id: $id) {
      id
      name
      gender
      email
    }
  }
`;

// getEmployees 쿼리 입니다.
const GET_EMPLOYEES = gql`
  query GetEmployees {
    getEmployees {
      id
      name
      gender
    }
  }
`;

// saveEmployee 뮤테이션 입니다.
const Save_Employee = gql`
  mutation SaveEmployee($employee: Employee!) {
    saveEmployee(employee: $employee) {
      id
    }
  }
`;

const Employee = () => {

    // useQuery 로 GET_EMPLOYEE 
    const { data: employee } = useQuery<Employee, EmployeeVars>(
        GET_EMPLOYEE,
        { variables: { id: "1" } }
    );
    
    const { data: employees } = useQuery<Employee[]>(
        GET_EMPLOYEES,
    );

    const [saveEmployee, data] = useMutation<Employee, Employee>(
        Save_Employee,
        { variables: { id: "1", name: "Smith", gender: "Male", email: "smith@ggg.com", address: "Dongjak-gu, Seoul" } }
    );
    
    return (
        <div className="Employee">
            ...
        </div>
    );
}



5. 총평

처음 GraphQL을 접했을 때는 REST API의 대체재라고 생각을 했습니다.
하지만, 실제로 GraphQL을 사용하면서 느낀 점은 대체재라기 보다는 보완재가 맞다는 생각이 들었습니다.
서버 개발자와 클라이언트 개발자가 스키마를 공통적으로 관리해야한다는 번거로움이 있지만 딱 필요한 데이터만 응답 받을 수 있는 GraphQL만의 강점이 있기 때문에 경우에 따라 REST API보다 더 나은 퍼포먼스를 제공할 수 있을 것으로 예상합니다.

wb91


참고 사이트