REST, GraphQL, gRPC 멀티 프로토콜 API 구축 방법

Ashley Innocent

Ashley Innocent

13 March 2026

REST, GraphQL, gRPC 멀티 프로토콜 API 구축 방법

요약 (TL;DR)

비즈니스 로직을 프로토콜 계층과 분리하여 멀티 프로토콜 API를 구축하세요. 공유 도메인 계층을 만들고, 그 위에 REST, GraphQL, gRPC 어댑터를 추가합니다. Modern PetstoreAPI는 세 가지 프로토콜에 걸쳐 일관된 데이터 모델을 사용하여 이러한 아키텍처를 보여줍니다.

소개

여러분의 API는 웹 클라이언트, 모바일 앱, 내부 마이크로서비스에 서비스를 제공합니다. 웹 클라이언트는 단순함을 위해 REST를 원합니다. 모바일 앱은 데이터 전송을 줄이기 위해 GraphQL을 원합니다. 마이크로서비스는 성능을 위해 gRPC를 원합니다. 세 가지 별도의 API를 구축하시겠습니까?

아닙니다. 세 가지 프로토콜 계층을 가진 하나의 API를 구축합니다. 비즈니스 로직은 동일하게 유지됩니다. 프로토콜 어댑터만 변경됩니다. 이것이 멀티 프로토콜 API 아키텍처입니다.

Modern PetstoreAPI는 공유 코어로부터 REST, GraphQL, gRPC를 구현합니다. 동일한 펫 스토어 로직이 일관된 동작으로 세 가지 프로토콜 모두에 서비스를 제공합니다.

💡
멀티 프로토콜 API를 구축하거나 테스트 중이라면, Apidog는 REST, GraphQL, gRPC 테스트를 하나의 도구에서 지원합니다. 모든 프로토콜이 일관된 데이터를 반환하는지 확인하고 프로토콜 간 워크플로우를 테스트할 수 있습니다.
button

이 가이드에서는 Modern PetstoreAPI를 참조하여 멀티 프로토콜 API를 설계하고, 프로토콜 계층을 구현하며, 프로토콜 간 일관성을 보장하는 방법을 배울 것입니다.

멀티 프로토콜 아키텍처

멀티 프로토콜 API는 관심사를 계층으로 분리합니다.

계층화된 아키텍처

┌─────────────────────────────────────────┐
│  프로토콜 계층 (REST/GraphQL/gRPC)        │
├─────────────────────────────────────────┤
│  애플리케이션 계층 (유스케이스)           │
├─────────────────────────────────────────┤
│  도메인 계층 (비즈니스 로직)              │
├─────────────────────────────────────────┤
│  데이터 계층 (데이터베이스, 캐시)         │
└─────────────────────────────────────────┘

프로토콜 계층: HTTP, GraphQL 쿼리, gRPC 호출 처리
애플리케이션 계층: 유스케이스 조정 (펫 생성, 주문 처리)
도메인 계층: 비즈니스 규칙 (유효성 검사, 계산)
데이터 계층: 영속성 (데이터베이스, 캐시)

핵심 원칙

1. 프로토콜 불가지론적 코어

비즈니스 로직은 HTTP, GraphQL 또는 gRPC에 대해 알지 못합니다. 도메인 객체로 작동합니다.

2. 얇은 프로토콜 어댑터

프로토콜 계층은 프로토콜 형식과 도메인 객체 간에 변환합니다. 비즈니스 로직을 포함하지 않습니다.

3. 공유 데이터 모델

모든 프로토콜은 내부적으로 동일한 도메인 모델을 사용하여 일관성을 보장합니다.

4. 독립적인 배포

필요한 경우 각 프로토콜을 개별적으로 배포할 수 있습니다.

공유 도메인 계층

도메인 계층은 모든 프로토콜에 걸쳐 공유되는 비즈니스 로직을 포함합니다.

도메인 모델

// 도메인 모델 (프로토콜 불가지론적)
class Pet {
  id: string;
  name: string;
  species: Species;
  status: PetStatus;
  price: number;

  constructor(data: PetData) {
    this.validate(data);
    Object.assign(this, data);
  }

  validate(data: PetData): void {
    if (!data.name || data.name.length < 2) {
      throw new ValidationError('Name must be at least 2 characters');
    }
    if (data.price < 0) {
      throw new ValidationError('Price cannot be negative');
    }
  }

  adopt(userId: string): Order {
    if (this.status !== PetStatus.AVAILABLE) {
      throw new BusinessError('Pet is not available for adoption');
    }
    this.status = PetStatus.ADOPTED;
    return new Order({
      petId: this.id,
      userId,
      total: this.price
    });
  }
}

이 모델은 REST, GraphQL, gRPC 모두에 작동합니다. 유효성 검사 및 비즈니스 규칙은 동일합니다.

유스케이스

// 유스케이스 (프로토콜 불가지론적)
class AdoptPetUseCase {
  constructor(
    private petRepository: PetRepository,
    private orderRepository: OrderRepository
  ) {}

  async execute(petId: string, userId: string): Promise {
    const pet = await this.petRepository.findById(petId);
    if (!pet) {
      throw new NotFoundError('Pet not found');
    }

    const order = pet.adopt(userId);
    await this.petRepository.save(pet);
    await this.orderRepository.save(order);

    return order;
  }
}

이 유스케이스는 REST, GraphQL 또는 gRPC에서 호출되는지 여부와 관계없이 작동합니다.

REST 프로토콜 계층

REST 계층은 HTTP 요청을 도메인 작업으로 변환합니다.

REST 컨트롤러

// REST 어댑터
class PetsController {
  constructor(private adoptPetUseCase: AdoptPetUseCase) {}

  async adoptPet(req: Request, res: Response): Promise {
    try {
      const { petId } = req.params;
      const { userId } = req.body;

      const order = await this.adoptPetUseCase.execute(petId, userId);

      res.status(201).json({
        id: order.id,
        petId: order.petId,
        userId: order.userId,
        total: order.total,
        status: order.status
      });
    } catch (error) {
      this.handleError(error, res);
    }
  }

  private handleError(error: Error, res: Response): void {
    if (error instanceof NotFoundError) {
      res.status(404).json({
        type: 'https://petstoreapi.com/errors/not-found',
        title: 'Not Found',
        status: 404,
        detail: error.message
      });
    } else if (error instanceof ValidationError) {
      res.status(400).json({
        type: 'https://petstoreapi.com/errors/validation-error',
        title: 'Validation Error',
        status: 400,
        detail: error.message
      });
    } else {
      res.status(500).json({
        type: 'https://petstoreapi.com/errors/internal-error',
        title: 'Internal Server Error',
        status: 500
      });
    }
  }
}

REST 라우트

app.post('/v1/pets/:petId/adopt', (req, res) =>
  petsController.adoptPet(req, res)
);

Modern PetstoreAPI REST 엔드포인트

GraphQL 프로토콜 계층

GraphQL 계층은 GraphQL 쿼리를 도메인 작업으로 변환합니다.

GraphQL 스키마

type Pet {
  id: ID!
  name: String!
  species: Species!
  status: PetStatus!
  price: Float!
}

type Order {
  id: ID!
  petId: ID!
  userId: ID!
  total: Float!
  status: OrderStatus!
}

type Mutation {
  adoptPet(petId: ID!, userId: ID!): Order!
}

GraphQL 리졸버

// GraphQL 어댑터
const resolvers = {
  Mutation: {
    adoptPet: async (
      _parent: any,
      args: { petId: string; userId: string },
      context: Context
    ): Promise => {
      try {
        return await context.adoptPetUseCase.execute(
          args.petId,
          args.userId
        );
      } catch (error) {
        throw new GraphQLError(error.message, {
          extensions: {
            code: error instanceof NotFoundError ? 'NOT_FOUND' :
                  error instanceof ValidationError ? 'BAD_USER_INPUT' :
                  'INTERNAL_SERVER_ERROR'
          }
        });
      }
    }
  }
};

GraphQL 요청

mutation {
  adoptPet(
    petId: "019b4132-70aa-764f-b315-e2803d882a24"
    userId: "user-123"
  ) {
    id
    total
    status
  }
}

Modern PetstoreAPI GraphQL 스키마

gRPC 프로토콜 계층

gRPC 계층은 Protocol Buffer 메시지를 도메인 작업으로 변환합니다.

프로토콜 버퍼 정의

syntax = "proto3";

package petstore.v1;

service PetService {
  rpc AdoptPet(AdoptPetRequest) returns (AdoptPetResponse);
}

message AdoptPetRequest {
  string pet_id = 1;
  string user_id = 2;
}

message AdoptPetResponse {
  string order_id = 1;
  string pet_id = 2;
  string user_id = 3;
  double total = 4;
  string status = 5;
}

gRPC 서비스 구현

// gRPC 어댑터
class PetServiceImpl implements IPetService {
  constructor(private adoptPetUseCase: AdoptPetUseCase) {}

  async adoptPet(
    call: ServerUnaryCall,
    callback: sendUnaryData
  ): Promise {
    try {
      const { petId, userId } = call.request;

      const order = await this.adoptPetUseCase.execute(petId, userId);

      callback(null, {
        orderId: order.id,
        petId: order.petId,
        userId: order.userId,
        total: order.total,
        status: order.status
      });
    } catch (error) {
      callback({
        code: error instanceof NotFoundError ? status.NOT_FOUND :
              error instanceof ValidationError ? status.INVALID_ARGUMENT :
              status.INTERNAL,
        message: error.message
      });
    }
  }
}

Modern PetstoreAPI gRPC 서비스

Modern PetstoreAPI가 멀티 프로토콜을 구현하는 방법

Modern PetstoreAPI는 실제 예시를 통해 멀티 프로토콜 아키텍처를 보여줍니다.

아키텍처 개요

Modern PetstoreAPI
├── 도메인 계층
│   ├── Pet (엔티티)
│   ├── Order (엔티티)
│   └── 유스케이스
│       ├── CreatePet
│       ├── AdoptPet
│       └── PlaceOrder
├── REST 계층
│   ├── /v1/pets
│   ├── /v1/orders
│   └── OpenAPI 3.2 스펙
├── GraphQL 계층
│   ├── Query 리졸버
│   ├── Mutation 리졸버
│   └── GraphQL 스키마
└── gRPC 계층
    ├── PetService
    ├── OrderService
    └── .proto 정의

일관된 데이터 모델

모든 프로토콜은 동일한 데이터를 반환합니다.

REST:

{
  "id": "019b4132-70aa-764f-b315-e2803d882a24",
  "name": "Fluffy",
  "species": "CAT"
}

GraphQL:

{
  "data": {
    "pet": {
      "id": "019b4132-70aa-764f-b315-e2803d882a24",
      "name": "Fluffy",
      "species": "CAT"
    }
  }
}

gRPC:

{
  pet_id: "019b4132-70aa-764f-b315-e2803d882a24"
  name: "Fluffy"
  species: CAT
}

동일한 데이터, 다른 형식.

공유 유효성 검사

유효성 검사는 도메인 계층에서 발생하므로 모든 프로토콜이 동일한 규칙을 적용합니다.

// 도메인 유효성 검사 (공유)
if (name.length < 2) {
  throw new ValidationError('Name must be at least 2 characters');
}

REST 반환:

{
  "type": "https://petstoreapi.com/errors/validation-error",
  "status": 400,
  "detail": "Name must be at least 2 characters"
}

GraphQL 반환:

{
  "errors": [{
    "message": "Name must be at least 2 characters",
    "extensions": {"code": "BAD_USER_INPUT"}
  }]
}

gRPC 반환:

code: INVALID_ARGUMENT
message: "Name must be at least 2 characters"

동일한 유효성 검사, 프로토콜별 오류 형식.

Apidog로 멀티 프로토콜 API 테스트

Apidog는 세 가지 프로토콜 모두 테스트를 지원합니다.

REST 엔드포인트 테스트

POST https://petstoreapi.com/v1/pets/019b4132-70aa-764f-b315-e2803d882a24/adopt
Content-Type: application/json

{
  "userId": "user-123"
}

GraphQL Mutation 테스트

mutation {
  adoptPet(
    petId: "019b4132-70aa-764f-b315-e2803d882a24"
    userId: "user-123"
  ) {
    id
    total
  }
}

gRPC 서비스 테스트

grpc.petstore.v1.PetService/AdoptPet
{
  "pet_id": "019b4132-70aa-764f-b315-e2803d882a24",
  "user_id": "user-123"
}

일관성 확인

세 가지 프로토콜 모두 동일한 데이터를 반환하는지 테스트합니다.

  1. REST 엔드포인트 호출
  2. GraphQL 뮤테이션 호출
  3. gRPC 서비스 호출
  4. 결과 비교

Apidog는 이 프로토콜 간 테스트를 자동화할 수 있습니다.

배포 전략

전략 1: 단일 서비스

모든 프로토콜을 하나의 서비스에 배포:

┌─────────────────────────┐
│  PetstoreAPI 서비스     │
│  ├── REST (포트 8080)   │
│  ├── GraphQL (포트 8081)│
│  └── gRPC (포트 50051)  │
└─────────────────────────┘

장점: 간단한 배포, 공유 리소스
단점: 모든 프로토콜이 함께 확장됨

전략 2: 별도 서비스

각 프로토콜을 별도로 배포:

┌──────────────┐  ┌──────────────┐  ┌──────────────┐
│ REST 서비스  │  │ GraphQL 서비스│  │ gRPC 서비스  │
│ (포트 8080)  │  │ (포트 8081)  │  │ (포트 50051) │
└──────────────┘  └──────────────┘  └──────────────┘
       │                 │                  │
       └─────────────────┴──────────────────┘
                         │
                  ┌──────────────┐
                  │ 공유 코어    │
                  │   라이브러리 │
                  └──────────────┘

장점: 독립적인 확장, 프로토콜 격리
단점: 더 복잡한 배포

전략 3: API 게이트웨이

API 게이트웨이를 사용하여 프로토콜별 백엔드로 라우팅:

                ┌─────────────┐
                │ API 게이트웨이│
                └─────────────┘
                       │
        ┌──────────────┼──────────────┐
        │              │              │
┌───────▼──────┐ ┌────▼─────┐ ┌──────▼──────┐
│ REST 백엔드  │ │ GraphQL  │ │ gRPC 백엔드 │
└──────────────┘ └──────────┘ └─────────────┘

장점: 중앙 집중식 라우팅, 속도 제한, 인증
단점: 추가적인 지연 시간, 게이트웨이 복잡성

Modern PetstoreAPI는 단순성을 위해 전략 1을 사용합니다.

결론

멀티 프로토콜 API는 비즈니스 로직을 중복하지 않고 클라이언트에게 유연성을 제공합니다. 프로토콜 계층을 도메인 로직과 분리함으로써, 공유 코어에서 REST, GraphQL, gRPC를 지원할 수 있습니다.

Modern PetstoreAPI는 일관된 데이터 모델, 공유 유효성 검사, 프로토콜별 어댑터를 통해 이 아키텍처를 보여줍니다. 클라이언트가 단순함을 위해 REST를 사용하든, 유연성을 위해 GraphQL을 사용하든, 성능을 위해 gRPC를 사용하든, 동일한 비즈니스 규칙을 가진 동일한 펫 스토어에 접근합니다.

프로토콜 간 일관성을 보장하려면 Apidog로 멀티 프로토콜 API를 테스트하세요. Modern PetstoreAPI를 탐색하여 멀티 프로토콜 아키텍처를 실제로 확인하세요.

button

자주 묻는 질문 (FAQ)

세 가지 프로토콜을 모두 지원해야 하나요?

아닙니다. 공개 API의 경우 REST로 시작하세요. 클라이언트가 유연한 데이터 페치를 필요로 하면 GraphQL을 추가하세요. 내부 마이크로서비스의 경우 gRPC를 추가하세요. 명확한 사용 사례가 있을 때만 프로토콜을 추가하세요.

프로토콜 일관성을 유지하려면 어떻게 해야 하나요?

도메인 계층에서 비즈니스 로직을 공유하세요. 프로토콜 어댑터는 형식만 변환해야 하며 비즈니스 규칙을 포함해서는 안 됩니다. 모든 프로토콜이 동일한 데이터를 반환하는지 확인하기 위해 테스트하세요.

프로토콜을 독립적으로 버전 관리할 수 있나요?

예. REST는 v2일 수 있지만 GraphQL은 v1일 수 있습니다. 하지만 이는 복잡성을 야기합니다. 가능하면 프로토콜을 동기화 상태로 유지하려고 노력하세요.

프로토콜 간 인증은 어떻게 처리하나요?

모든 프로토콜에서 동일한 인증을 사용하세요. REST는 헤더에 Bearer 토큰을 사용합니다. GraphQL도 동일한 토큰을 사용합니다. gRPC는 메타데이터를 사용합니다. 도메인 계층은 동일한 방식으로 토큰의 유효성을 검사합니다.

WebSocket과 SSE는 어떻게 되나요?

WebSocket과 SSE는 API 프로토콜이 아닌 전송 프로토콜입니다. 실시간 업데이트를 위해 REST/GraphQL/gRPC와 함께 추가할 수 있습니다. Modern PetstoreAPI는 둘 다 포함합니다.

멀티 프로토콜 API를 어떻게 문서화하나요?

REST에는 OpenAPI를, GraphQL에는 GraphQL 스키마를, gRPC에는 .proto 파일을 사용하세요. Modern PetstoreAPI는 이 세 가지 모두를 https://docs.petstoreapi.com/ 에서 제공합니다.

다른 프로토콜에 대해 다른 데이터베이스를 사용할 수 있나요?

예, 하지만 일관성 문제가 발생합니다. 모든 프로토콜에 동일한 데이터 계층을 사용하는 것이 좋습니다. 프로토콜 어댑터가 형식 차이를 처리하도록 하세요.

멀티 프로토콜 API는 어떻게 테스트하나요?

하나의 도구에서 모든 프로토콜을 테스트하려면 Apidog를 사용하세요. 프로토콜 간 일관성을 확인하는 테스트 스위트를 만드세요. REST, GraphQL, gRPC가 동일한 작업에 대해 동일한 데이터를 반환하는지 테스트하세요.

Apidog에서 API 설계-첫 번째 연습

API를 더 쉽게 구축하고 사용하는 방법을 발견하세요