Backend

[Backend] GraphQL

아몬드맛빼빼로 2024. 11. 25. 12:32
반응형

GraphQL 개요

GraphQL의 주요 특징

  1. 단일 엔드포인트로의 유연한 접근
    GraphQL은 단일 엔드포인트(/graphql)를 통해 모든 데이터 요청을 처리한다. REST API처럼 여러 엔드포인트를 관리해야 하는 복잡성을 줄이고, 클라이언트가 특정 데이터에 대한 요청을 자유롭게 구성할 수 있다.
  2. 필요한 데이터만 요청 가능
    GraphQL은 클라이언트가 필요한 데이터만 요청하도록 설계되었다. 불필요한 데이터를 전송받을 필요 없이 원하는 필드를 명시적으로 요청할 수 있어 네트워크 사용을 효율화할 수 있다.
  3. 타입 시스템을 통한 데이터 일관성 보장
    GraphQL 스키마는 데이터의 구조와 타입을 명확하게 정의한다. 이를 통해 개발자는 API가 어떤 데이터를 주고받는지 쉽게 이해할 수 있으며, 런타임 오류를 줄일 수 있다.
  4. Subscription을 통한 실시간 데이터
    GraphQL은 Subscription을 이용하여 실시간 데이터를 처리할 수 있다. 이는 주식 가격 변동이나 채팅 메시지와 같은 실시간 데이터 업데이트에 유용하다.

GraphQL 사용 예제

1. 데이터 조회 (Query)

특정 책 정보를 조회하는 예제이다.

query {
    bookById(id: "1") {
        title
        author
    }
}
{
    "data": {
        "bookById": {
            "title": "GraphQL 배우기",
            "author": "김명현"
        }
    }
}

2. 데이터 생성 (Mutation)

새로운 책을 생성하는 예제이다.

mutation {
    createBook(input: { title: "GraphQL의 모든 것", author: "김철수" }) {
        id
        title
        author
    }
}
{
    "data": {
        "createBook": {
            "id": "2",
            "title": "GraphQL?",
            "author": "권재헌"
        }
    }
}

3. 데이터 수정 (Mutation)

기존 책 정보를 수정하는 예제이다.

mutation {
    updateBook(id: "2", input: { title: "GraphQL 심화 가이드" }) {
        id
        title
        author
    }
}
{
    "data": {
        "updateBook": {
            "id": "2",
            "title": "GraphQL!",
            "author": "김태은"
        }
    }
}

4. 데이터 삭제 (Mutation)

책을 삭제하는 요청이다.

mutation {
    deleteBook(id: "2")
}
{
    "data": {
        "deleteBook": true
    }
}

REST API와 GraphQL 비교

특징 REST API GraphQL
엔드포인트 여러 개의 엔드포인트 필요 단일 엔드포인트 (/graphql)
데이터 요청 고정된 데이터 전송 필요한 데이터만 선택적 요청 가능
데이터 일관성 데이터 형식 명시적 정의 부족 스키마로 데이터 형식 명확하게 정의
불필요한 데이터 전체 리소스를 반환 필요한 필드만 반환
실시간 데이터 별도 구현 필요 Subscription으로 지원

Spring Boot와 GraphQL 

Spring Boot와 GraphQL을 결합하여 사용하는 방법을 살펴보자. 이를 통해 어떻게 GraphQL을 백엔드에 통합하고 API를 구현하는지 알 수 있다.

1. 의존성 추가

Spring Boot 프로젝트에 GraphQL을 사용하려면, build.gradle에 다음과 같은 의존성을 추가한다.

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-graphql")
    implementation("com.graphql-java:graphql-spring-boot-starter")
}

2. GraphQL 스키마 정의

src/main/resources/graphql 폴더에 .graphqls 파일을 생성하여 스키마를 정의한다. 예를 들어 book.graphqls 파일에 다음과 같이 작성할 수 있다.

type Query {
    bookById(id: ID!): Book
    allBooks: [Book]
}

type Mutation {
    createBook(input: CreateBookInput!): Book
    updateBook(id: ID!, input: UpdateBookInput!): Book
    deleteBook(id: ID!): Boolean
}

type Book {
    id: ID!
    title: String!
    author: String!
}

input CreateBookInput {
    title: String!
    author: String!
}

input UpdateBookInput {
    title: String
    author: String
}

3. 서비스 및 리졸버 작성

GraphQL 요청을 처리하는 리졸버 클래스를 작성하여, 비즈니스 로직을 구현한다. 예를 들어 책 정보를 관리하는 서비스를 작성할 수 있다.

@Service
class BookService {
    private val books = mutableListOf<Book>()

    fun getBookById(id: String): Book? {
        return books.find { it.id == id }
    }

    fun getAllBooks(): List<Book> {
        return books
    }

    fun createBook(input: CreateBookInput): Book {
        val book = Book(UUID.randomUUID().toString(), input.title, input.author)
        books.add(book)
        return book
    }

    fun updateBook(id: String, input: UpdateBookInput): Book? {
        val book = books.find { it.id == id }
        book?.apply {
            title = input.title ?: title
            author = input.author ?: author
        }
        return book
    }

    fun deleteBook(id: String): Boolean {
        return books.removeIf { it.id == id }
    }
}

4. 리졸버 연결

리졸버 클래스를 작성하여 GraphQL 스키마와 서비스 로직을 연결한다.

@Component
class BookResolver(private val bookService: BookService) {

    @QueryMapping
    fun bookById(@Argument id: String): Book? {
        return bookService.getBookById(id)
    }

    @QueryMapping
    fun allBooks(): List<Book> {
        return bookService.getAllBooks()
    }

    @MutationMapping
    fun createBook(@Argument input: CreateBookInput): Book {
        return bookService.createBook(input)
    }

    @MutationMapping
    fun updateBook(@Argument id: String, @Argument input: UpdateBookInput): Book? {
        return bookService.updateBook(id, input)
    }

    @MutationMapping
    fun deleteBook(@Argument id: String): Boolean {
        return bookService.deleteBook(id)
    }
}