여러 서비스 간 API 엔드포인트 공유하기

이번 장에서는 여러 서비스 간에 API Gateway를 활용하는 방법을 살펴보겠습니다. API를 여러 서비스로 분할할 때 직면하는 문제 중 하나는 동일한 도메인을 공유하는 것입니다. Serverless Framework 서비스의 일부로 생성된 API는 다음과 같은 고유한 URL을 갖는다는 것을 기억할 것입니다:

https://z6pv80ao4l.execute-api.us-east-1.amazonaws.com/dev

API에 커스텀 도메인을 연결하면 위와 같은 특정 엔드포인트에 연결됩니다. 이는 여러 API 서비스를 생성할 경우 각각 고유한 엔드포인트를 갖게 된다는 것을 의미합니다.

커스텀 도메인에 대해 다른 기본 경로를 할당할 수 있습니다. 예를 들어, api.example.com/notes는 하나의 서비스를 가리키고, api.example.com/billing은 다른 서비스를 가리킬 수 있습니다. 하지만 notes 서비스를 분할하려고 하면 커스텀 도메인을 공유하는 문제에 직면하게 됩니다.

우리의 노트 앱에는 notes-apibilling-api라는 두 개의 서비스가 있습니다. 이번 장에서는 두 서비스가 단일 API 엔드포인트를 통해 제공되도록 API Gateway를 구성하는 방법을 살펴보겠습니다.

설정하려는 API 경로는 다음과 같습니다:

  • notes-api 모든 노트 목록 조회 ⇒ GET https://api.example.com/notes
  • notes-api 특정 노트 조회 ⇒ GET https://api.example.com/notes/{noteId}
  • notes-api 노트 생성 ⇒ POST https://api.example.com/notes
  • notes-api 노트 업데이트 ⇒ PUT https://api.example.com/notes/{noteId}
  • notes-api 노트 삭제 ⇒ DELETE https://api.example.com/notes/{noteId}
  • billing-api 결제 ⇒ POST https://api.example.com/billing

API Gateway에서 경로가 작동하는 방식

API Gateway는 약간 까다로운 방식으로 구조화되어 있습니다. 이를 자세히 살펴보겠습니다.

  • 각 경로 부분은 별도의 API Gateway 리소스 객체입니다.
  • 그리고 경로 부분은 이전 부분의 자식 리소스입니다.

따라서 /notes 경로 부분은 /의 자식 리소스입니다. 그리고 /notes/{noteId}/notes의 자식 리소스입니다.

우리의 설정에 따르면, billing-api/billing 경로를 가지길 원합니다. 그리고 이는 /의 자식 리소스가 됩니다. 그러나 /notes-api 서비스에서 생성됩니다. 따라서 서비스 간에 리소스를 공유할 방법을 찾아야 합니다.

Notes Service

이를 위해 notes-api는 API Gateway 프로젝트와 루트 경로 /를 공유해야 합니다.

serverless-stack-demo-ext-api 저장소에서 services/notes-api/ 디렉토리로 이동합니다. serverless.yml 파일의 끝 부분을 보면 다음과 같은 내용이 있습니다:

...

- Outputs:
    ApiGatewayRestApiId:
      Value:
        Ref: ApiGatewayRestApi
      Export:
        Name: ${self:custom.stage}-ExtApiGatewayRestApiId

    ApiGatewayRestApiRootResourceId:
      Value:
         Fn::GetAtt:
          - ApiGatewayRestApi
          - RootResourceId 
      Export:
        Name: ${self:custom.stage}-ExtApiGatewayRestApiRootResourceId

여기서 무엇을 하고 있는지 살펴보겠습니다.

  1. 공유해야 하는 첫 번째 크로스 스택 참조는 이 서비스의 일부로 생성된 API Gateway ID입니다. ${self:custom.stage}-ExtApiGatewayRestApiId라는 이름으로 이를 내보냅니다. 모든 환경/스테이지에서 내보내기가 작동하도록 스테이지 이름을 포함합니다. 이 내보내기의 값은 현재 스택에서 ApiGatewayRestApi라는 참조로 사용할 수 있습니다.
  2. 또한 RootResourceId를 내보내야 합니다. 이는 이 API Gateway 프로젝트의 / 경로에 대한 참조입니다. 이 ID를 가져오기 위해 Fn::GetAtt CloudFormation 함수를 사용하고 현재 ApiGatewayRestApi를 전달한 후 RootResourceId 속성을 조회합니다. ${self:custom.stage}-ExtApiGatewayRestApiRootResourceId라는 이름으로 이를 내보냅니다.

결제 서비스

위 내용을 어떻게 가져오는지 살펴보겠습니다. services/ 디렉토리에서 billing-api 서비스를 열어보세요.

...

provider:
  apiGateway:
    restApiId: !ImportValue ${self:custom.stage}-ExtApiGatewayRestApiId
    restApiRootResourceId: !ImportValue ${self:custom.stage}-ExtApiGatewayRestApiRootResourceId
...

functions:
  billing:
    handler: billing.main
    events:
      - http:
          path: billing
          method: post
          cors: true
          authorizer: aws_iam

notes-api 서비스와 동일한 API Gateway 도메인을 공유하기 위해 provider: 블록에 apiGateway: 섹션을 추가합니다.

  1. 여기서 notes 서비스의 restApiId를 사용하겠다고 명시합니다. 이전에 내보낸 크로스 스택 참조 !ImportValue ${self:custom.stage}-ExtApiGatewayRestApiId를 사용하여 이를 수행합니다.

  2. 또한 이 서비스의 모든 API가 notes 서비스의 루트 경로 아래에 연결되도록 설정합니다. 이전에 내보낸 크로스 스택 참조 !ImportValue ${self:custom.stage}-ExtApiGatewayRestApiRootResourceIdrestApiRootResourceId로 설정하여 이를 수행합니다.

이제 billing-api 서비스를 배포할 때, Serverless Framework는 새로운 API Gateway 프로젝트를 생성하는 대신 가져온 프로젝트를 재사용합니다.

이 설정에서 주목할 점은 API Gateway가 이 서비스에서 생성된 라우트를 어디에 연결할지 알아야 한다는 것입니다. /billing 경로를 API Gateway 프로젝트의 루트에 연결하려고 합니다. 따라서 restApiRootResourceIdnotes-api 서비스의 루트 리소스를 가리킵니다. 물론 이렇게 할 필요는 없습니다. /billing 경로를 메인 API 서비스에서 생성하고 여기서 연결하도록 서비스를 구성할 수도 있습니다.

의존성

API Gateway 프로젝트를 공유함으로써 billing-apinotes-api에 의존하게 됩니다. 배포할 때는 notes-api를 먼저 배포해야 합니다.

제약 사항

경로 부분은 한 번만 생성할 수 있습니다. 이 동작 방식을 이해하기 위해 예제를 살펴보겠습니다. 다음과 같은 엔드포인트를 사용하는 새로운 API 서비스를 추가해야 한다고 가정해 보겠습니다.

https://api.example.com/billing/xyz

이 새로운 서비스는 notes-api에서 /를 가져올 수 없습니다.

이는 Serverless Framework가 다음과 같은 두 가지 경로 부분을 생성하려고 시도하기 때문입니다:

  1. /billing
  2. /billing/xyz

하지만 /billing은 이미 billing-api 서비스에서 생성되었습니다. 따라서 이 새로운 서비스를 배포하려고 하면 CloudFormation이 실패하고 리소스가 이미 존재한다는 오류를 발생시킵니다.

새로운 서비스는 /billingbilling-api에서 가져와야 하며, 이렇게 하면 새로운 서비스는 /billing/xyz 부분만 생성하면 됩니다.

이제 서비스 구성이 완료되었고 배포할 준비가 되었습니다. 요약하자면, 리소스 저장소와 API 저장소에 몇 가지 의존성이 있습니다.