들어가며

개발자들은 하나의 애플리케이션을 구현하기 위해 다양한 외부 시스템을 사용하게 됩니다. 닥터차 서비스만 하더라도 이메일, 푸시알림, 알림톡, 슬랙 등 알림 관련 외부 시스템부터 가상번호, 자동차세 납부 내역, 공공데이터포털 등 비즈니스에 밀접한 외부 시스템까지 다양한 외부 시스템을 사용하고 있습니다.

대부분 이런 외부 시스템들은 호출 가능한 공개 API를 제공합니다. API 하나 호출하는데 뭐 복잡할게 있나 싶지만, 시스템마다 제공하는 API의 명세 방식이 다르기도 하고, 제공되는 API의 형태가 내부적으로 사용하는 유즈케이스와 어긋나 한 유즈케이스 구현을 위해 여러개 API를 순차 호출 해야 할 수도 있습니다. 또 날짜와 같이 동일한 데이터라도 시스템마다 입력, 반환하는 형태가 다르기도 하죠. 프로바이더 하나에 이 모든 역할을 집어 넣다보면 엄청나게 거대한 프로바이더가 탄생하기 십상입니다.

또한 외부 시스템을 사용하는 클라이언트는 위 모든 역할에 대해 알아야 할 필요가 없고, 알아서도 안 됩니다. 외부 시스템 모듈이 추상화된 유즈케이스들에 대한 인터페이스만을 제공하고, 그 내부 구현은 숨기는 것이 좋은 코드입니다.

이 글에서는 외부 API 호출에 어떤 역할들이 존재하는지 알아보고, 각 역할을 전용 객체에 분리하여 관리하는 방법에 대해 알아봅니다. 오토피디아에서는 Nest.js를 사용하여 각 객체를 필요한 곳에 inject하여 의존성을 관리하지만, 이 글의 핵심은 Nest.js가 아닌 역할 분리이므로 Node.js 뿐만 아니라 다른 어떤 프레임워크/언어에도 적용할 수 있습니다.

외부 API 호출에 필요한 역할

외부 API 호출에는 크게 다음 네가지 역할이 필요합니다.

  1. 파사드(Facade): 외부 API 호출.
  2. 서비스(Service): 내부 시스템에 제공되는 인터페이스.
  3. 번역기(Translator): 외부 시스템과 내부 인터페이스간의 데이터 변환.
  4. 어댑터(Adapter): 인터페이스 구현을 위한 파사드와 번역기 호출.

외부 시스템 호출에 필요한 역할 다이어그램

외부 시스템 호출에 필요한 역할 다이어그램

각 역할에 대해 하나씩 자세히 알아봅시다.

<aside> 💡 각 역할과 이를 담당하는 객체의 이름은 DDD의 컨텍스트 맵 중 하나인 부패 방지 계층(Anticorruption Layer)의 구현을 참고하였습니다. DDD의 바운디드 컨텍스트에 대해서는 모두를 위한 DDD 글에서 다룬 적이 있으나, 바운디드 컨텍스트 간의 관계인 컨텍스트 맵에 대해서는 향후 별도의 글로 다뤄볼 예정입니다!

</aside>

파사드(Facade)

API 또는 호출 케이스 별 메서드 구현

파사드에서는 최대한 변수명 등을 변경하지 않고 외부 API를 호출하나, 반드시 한 메서드가 한 API를 담당할 필요는 없습니다. 한 외부 API가 너무 많은 역할을 담당하고 있거나, 호출 케이스 별로 요구하는 파라미터가 다르다면 역할 별/호출 케이스 별로 메서드를 분리하는게 좋습니다.

예를 들어 가상번호 서비스를 제공하는 콜믹스에서는 모든/매핑된/매핑되지 않은 가상 번호 레코드 목록을 파라미터를 변경해가며 하나의 API로 조회하도록하나, 이를 호출하는 CallmixFacade에서는 각 케이스 별로 getAllVirtualNumberRecords, getMappedVirtualNumberRecords, getUnusedVirtualNumberRecords로 나누어 구현하였습니다.

요청/응답 형식 관리 및 에러 핸들링

파사드는 필요하다면 요청 파라미터들을 표현하는 타입을 정의하여 사용할 수 있습니다.