티스토리 뷰
들어가기 전에
안녕하세요, Shopping Service API팀 강희정입니다.
제목에서부터 유추 가능하듯이, 이번 글에서는 제가 속해 있는 Shopping Service API팀에 대해 이야기해 보고자 합니다.
약 1년 전에 지마켓에 입사한 이후로, 종종 전 직장 동료들을 만나거나 유사한 업계에 종사하는 다른 친구들을 만날 때 항상 듣던 말이 있었습니다. 물론 지금도 간간히 듣는 말이기도 합니다.
Shopping Service API팀? 뭐 하는 팀이야?
(과장 없이, 여태까지 만난 사람들 중 무슨 일을 하는 팀인지 맞춘 사람은 단 한 명뿐이었습니다. 여러분들도 어떤 일을 하는 팀인지 맞춰 보세요!)
API는 API고 Shopping Service API는 말 그대로 쇼핑과 관련된 정보를 제공해 주는 API인가? 하는 생각이 드실 텐데 틀린 말은 아닙니다. 다만 이는 굉장히 포괄적인 표현으로, 저희 팀을 설명하기엔 턱없이 부족합니다. 마치 지마켓을 보고 온라인 커머스 플랫폼이 아니라 단순히 웹/앱 서비스라고 정의하는 것처럼요.
저희 Shopping Service API팀은 다양한 업무를 담당하고 있지만 그중에서도 가장 큰 골자를 골라 보라면 facade API 제공이라고 할 수 있을 것 같습니다. facade API를 듣고서 저희 팀이 어떤 일을 하는지 단번에 파악하신 분들도 계시겠지만 여전히 의문이 남아있는 분들도 계시리라 생각됩니다. facade API란 무엇이고 도대체 이걸 어떻게 업무에 적용한다는 것인지 궁금하실 텐데요, 지금부터 차근차근 설명해 드리도록 하겠습니다.
facade API
facade API의 어원이 되는 facade(퍼사드)는 건물의 출입구로 이용되는 정면 외벽 부분을 가리키는 말로, facade API는 이러한 특징을 가진 API를 의미합니다.
apigee의 API Facade Pattern에서는 API Facade Design Pattern을 아래와 같이 설명하고 있습니다.
Facade 디자인 패턴은 백엔드 및 내부 시스템이 애플리케이션 개발자들에게 직접 노출되기에 너무 복잡할 때 생기는 문제를 해결하기 위한 솔루션이다.
The API Facade Design Pattern is a solution to a design problem that arises for API designers when back-end and internal systems of record are too complex to expose directly to application developers
또한, 위키백과에서는 퍼사드 패턴을 아래와 같이 설명하고 있습니다.
퍼사드는 클래스 라이브러리 같은 어떤 소프트웨어의 다른 커다란 코드 부분에 대한 간략화된 인터페이스를 제공하는 객체이다.
facade 패턴은 디자인 패턴에 해당하며, 이 개념을 API에 적용한 것이 facade API라 할 수 있습니다.
정리하자면, facade API는 API를 사용하는 애플리케이션 개발자로부터 복잡한 백엔드 시스템을 감추고 추상화하여, 애플리케이션 개발자가 백엔드 시스템을 비교적 간단한 API로 사용할 수 있게 하는 인터페이스라고 할 수 있습니다.
facade API를 사용하는 클라이언트와 백엔드 시스템은 아래와 같은 관계를 형성하고 있습니다.
facade API가 없을 때, 클라이언트가 백엔드 시스템의 API를 호출하기 위해서는 직접 해당 백엔드 시스템의 API를 호출해야 했습니다. facade API가 적용된 구조라면, 클라이언트가 백엔드 시스템의 API를 호출할 때 직접 호출하는 것이 아니라 facade API를 통해 간접적으로 백엔드 시스템을 호출하게 됩니다.
왜 facade API를 사용해야 할까?
그렇다면 왜 facade API를 사용해야 할까요? 언뜻 보면 클라이언트와 백엔드 시스템 사이에 불필요한 단계가 추가된 것 같아 보이지만, facade API 계층을 추가함으로써 생기는 장점도 있고, 일반적인 클라이언트 서버 모델에서 발생할 수 있는 여러 문제를 해결할 수 있게 됩니다.
시스템 통합에 따른 의존관계 역전
facade API는 여러 백엔드 시스템을 통합하는 일을 수행합니다. 여러 군데로 흩어져 있던 백엔드 시스템을 facade API에서 통합해 주기에 클라이언트 입장에서는 백엔드 시스템을 일일이 찾아가지 않아도 되는 것이죠.
facade API가 없는 일반적인 서버-클라이언트 구조라면, 클라이언트는 아래와 같이 백엔드 시스템에 직접 의존하게 됩니다.
이 경우, 클라이언트는 백엔드 시스템이 어떻게 구성되어 있는지 세부사항에 대해 알아야 하고, 필요한 기능을 호출하기 위해서 직접 백엔드 시스템을 호출해야 합니다.
객체 지향 프로그래밍에서의 의존관계 역전 원칙에서는 상위 모듈이 하위 모듈에 의존하는 전통적인 의존관계를 역전시키는 것에 대해 설명하고 있습니다. 여기서 말하는 상위 모듈과 하위 모듈을 간단하게 정리하자면 다음과 같습니다.
- 상위 모듈
- 복잡한 비즈니스 정책을 결정하는 계층
- 사용자 입력을 처리
- 시스템의 주요 작업을 조정
- 하위 모듈
- 상위 모듈에 의해 사용됨
- 구체적인 기능을 담당
- 시스템의 하위 수준 작업을 수행
모듈 간의 관계는 상대적이기에 시스템을 구성하는 데 있어 어떤 것이 상위 모듈이고 하위 모듈인지 항상 정해져 있는 것은 아니지만, 일반적인 경우 클라이언트를 상위 모듈, 서버를 하위 모듈이라고 할 수 있습니다.
앞서 말씀드린 일반적인 서버-클라이언트 구조에서는 상위 모듈인 클라이언트가 하위 모듈인 백엔드 시스템에 의존하는 구조를 가지고 있습니다. 상위 모듈 → 하위 모듈로 가는 의존 관계가 설정되어 있는 상태인 것이죠.
하지만 여기에 facade API가 추가되면 의존관계에 변화가 생기게 됩니다.
클라이언트는 facade API를 호출해야 백엔드 시스템을 이용할 수 있으므로 facade API에 의존하고 있고, facade API는 백엔드 시스템을 호출한 뒤 통합해야 하기 때문에 facade API는 백엔드 시스템들에 의존적입니다. 의존성 흐름은 기존 서버-클라이언트 구조와 유사하게 클라이언트 → facade API → 백엔드 시스템으로 흘러가고 있습니다.
다만 기존에 상위 모듈에 해당했던 클라이언트가 facade API를 만나면서 상대적으로 하위 모듈의 역할을 하게 됩니다. 클라이언트가 시스템의 주요 작업을 조정하고 사용자의 요청을 처리하는 데는 변함이 없지만, facade API는 이러한 클라이언트의 요청을 받아 백엔드 시스템의 여러 서브시스템을 조정하고 관리하며, 클라이언트와 백엔드 시스템 사이의 중재자 역할을 하기에 facade API가 상대적으로 상위 모듈에 해당하게 됩니다.
따라서 의존 관계의 흐름은 하위 모듈 → 상위 모듈로 흘러가, 의존관계 역전이 일어나게 됩니다.
상위 모듈은 하위 모듈에 비해 더 추상적이고 변동성이 적은 특징을 가지고 있습니다. 의존관계가 역전되면 더 추상적이고 변동성이 적은 쪽에 의존하게 되므로 모듈 간의 결합이 느슨해지며, 변경을 하는 데 있어 제약사항이 줄어드는 것이기에 시스템이 유연해지고 확장하기 쉬워집니다. facade API를 사용하게 되면 시스템 간의 의존관계를 역전시킬 수 있으며, 유지보수하기 용이한 시스템을 설계하고 운영할 수 있습니다.
변경에 덜 민감해진다
앞서 말씀드린 의존관계 역전에서 파생된 개념으로, facade API를 사용하면 변경에 덜 민감해지게 됩니다. 비즈니스 로직을 담당하고 있는 상위 모듈이 변경되면 이를 사용해야 하는 하위 모듈이 변경되어야 하지만, 구체적인 하위 모듈이 변경된다고 해서 상위 모듈에 큰 영향을 끼치지는 않기 때문입니다.
조금 더 이해하기 쉽게 코드로 예시를 들어 보도록 하겠습니다.
// 상품 정보를 담고 있는 클래스
// 시스템의 하위 수준에 있으며 구체적인 내용에 해당하는 하위 모듈
class Item {
public List<Item> getItemLists() {
return new ArrayList<Item>(); // 빈 리스트가 아닌 아이템이 있는 리스트로 가정
}
}
// 상품을 표시하는 페이지 관련 클래스
// 시스템의 상위 수준에 있으며 비즈니스 로직에 해당하는 상위 모듈
class Page {
public void generatePage() {
Item item = new Item();
// List<item> 형식의 리스트를 순회
List<Item> itemList = item.getItemLists();
for (Item i : itemList) {
// 각각의 아이템 페이지 추가
}
}
}
상품과 관련된 기능을 제공하는 Item 클래스에서는 상품 리스트를 리턴하는 getItemLists()라는 메소드를 가지고 있습니다. 상품 리스트를 표시하는 페이지인 Page 클래스에서는 generatePage() 메소드를 통해 페이지를 만드는 과정에서 상품 리스트를 받아와 페이지에 상품을 추가합니다.
이렇게 잘 운영되던 시스템에서 자그마한 이슈가 발생하게 됩니다. 판매자가 중복된 상품을 많이 등록하여 List에 중복된 상품이 굉장히 많이 들어가 사용자가 불편함을 느끼게 되는 상황입니다. 이에 List 대신 HashSet을 사용하여 중복된 상품이 들어가지 않도록 수정해야 합니다.
Item의 List를 반환하는 getItemLists가 List이 아닌 Set을 반환하도록 수정해 줍시다.
class Item {
public Set<Item> getItemLists() {
return new HashSet<Item>();
}
}
getItemLists()의 리턴 타입이 변경됨에 따라, 이를 참조하여 페이지를 구성하는 generatePage()에서도 List가 아닌 HashSet을 받도록 변경해야 합니다.
class Page {
public void generatePage() {
Item item = new Item();
// 변경된 부분 : List<Item> itemList -> Set<Item> itemSet
Set<Item> itemSet = item.getItemLists();
for (Item i : itemSet) {
// 각각의 아이템 페이지 추가
}
}
}
하위 모듈인 getItemLists() 메소드가 변경됨에 따라, 상위 모듈인 generatePage() 메소드의 내부가 변경되어야 하는 상황입니다.
이러한 상황에서 facade API를 사용하면 아래와 같이 변경할 수 있습니다.
// 상품 정보를 담고 있는 클래스
// Page 클래스와 비교했을때는 상위 모듈이지만, Facade 클래스에 비교했을 때는 하위 모듈
class Item {
public Set<Item> getItemLists() {
// 새 HashSet<Item>을 반환합니다.
return new HashSet<Item>();
}
}
// facade API 클래스
// Page 클래스의 상위 모듈
class Facade {
public List<Item> getItemLists() {
// HashSet인 getItemLists()를 List<Item>으로 변환해서 내려줌
}
}
// 상품을 표시하는 페이지 관련 클래스
class Page {
public void generatePage() {
Facade facade = new Facade();
List<Item> itemList = facade.getItemLists();
for (Item i : itemList) {
// 각각의 아이템 페이지 추가
}
}
}
Page가 Facade를 참조하여 Facade의 getItemLists() 메소드를 호출하도록 수정하였습니다. Facade는 getItemLists() 메소드를 통해 HashSet 타입인 Item의 getItemLists()를 List 타입으로 변환시켜 줍니다.
이로써 Item이 변경되어도 Item과 Facade에서만 수정이 일어날 뿐, Page까지 수정사항이 전이되지 않는 구조가 되었습니다.
언뜻 보기에는 두 방법 다 모두 2개의 메소드를 변경한다는 점에서 다를 것이 없어 보일 수 있습니다. 하지만 이는 이해를 돕기 위해 아주 간단한 메소드로 구현을 한 것이고, 실제 업무에서는 메소드 단위가 아니라 각각 프로젝트 단위로 변경이 일어난다고 보시면 됩니다.
백엔드 시스템에 해당하는 Item에서 변경이 일어났을 경우, 서버-클라이언트 구조에서는 네이티브 앱 클라이언트인 Page에서 이루어져야 하는데, 네이티브 앱은 업데이트가 일어날 경우 앱 스토어를 통해 다운로드를 받아야 하고 구버전 관리에 어려움이 있는 등, 변경이 어렵다는 특징을 가지고 있습니다.
Facade API가 적용되었을 경우, 백엔드 시스템에 해당하는 Item에서 변경이 일어났을 때, 네이티브 앱에 비해 비교적 변경이 쉬운 웹 앱으로 구성된 Facade API에서만 수정이 일어나게 됩니다. 따라서 변경사항에 대해 더 유연하게 대처할 수 있게 됩니다.
이와 같이 facade 패턴은 여러 시스템을 통합해서 사용할 때 유용한 디자인 패턴으로, 위와 같은 특징들로 인해 facade API는 MSA 구조에서 꼭 필요한 계층이라고 할 수 있습니다.
facade API와 Shopping Service API팀
facade API
저희 Shopping Service API팀에서는 지마켓과 옥션을 구성하고 있는 여러 백엔드 시스템으로부터 API를 제공받아 통합하고 변환하여 클라이언트단에 제공하는 facade API를 운영하고 있습니다.
상품 기본 정보, 가격, 할인, 배송, 쿠폰, 슈퍼딜 등등 지마켓과 옥션을 구성하고 있는 여러 서비스들을 웹과 모바일에서 사용하는 뷰 모델에 맞게 변환하여 클라이언트단에서 사용하기 용이하게 내려주고 있습니다.
facade API 계층이 있기에 클라이언트단에 해당하는 웹과 앱 개발자 입장에서는 api를 호출할 때 각각의 도메인을 직접 찾아 호출하는 것이 아니라 facade API만 바라보고 작업을 할 수 있게 됩니다. 위 그림은 Datadog에서 facade API의 time spent에 해당하는데, facade API가 호출하는 API 수가 굉장히 많은 것을 확인하실 수 있습니다. 클라이언트단에서 이 모든 API에 의존성을 가지고 있다고 생각해 보세요. 상상만 해도 끔찍하지 않나요!
facade API가 존재하기 전에는 비즈니스 로직과 facade API 기능이 혼재되어 있어 개발 복잡도가 높았고, 유지보수 비용이 상당했습니다. MSA가 등장함에 따라 각각의 비즈니스 로직들은 각각의 api로 분리되었고, facade 기능을 하는 별도의 API가 만들어진 것이죠. 기존에는 비즈니스 로직과 facade API가 함께였기에 한 팀에서 이 모든 것을 관리해야 했지만, API가 분리됨에 따라 담당 비즈니스별로 조직이 나뉘고 facade API를 담당하는 저희 팀이 생긴 것처럼, 조직이 경량화되었습니다.
홈 모듈화 플랫폼
또한, 저희 팀에서는 지마켓 홈 화면에 제공하는 모듈화 플랫폼을 운영하고 있습니다. 사실 홈 모듈화 플랫폼은 facade API만을 위한 플랫폼은 아니고, 홈 화면을 구성하는데 필요한 모듈들을 여러 조건에 맞게 조합하여 내려주는 플랫폼입니다.
홈 모듈화 플랫폼은 상품, 쿠폰, 배너 등의 여러 데이터를 베스트, 슈퍼딜, 개인화 등 속성별로 모아 앱과 웹에서 이를 그릴 수 있도록 템플릿화 하여 제공하고 있습니다.
어떻게 모듈들을 조합하여 제공할지, 어떤 조건으로 보여줄지에 대한 로직을 가지고 있기에 facade API만을 위한 솔루션이라고는 할 수 없지만, 홈 모듈화 플랫폼을 사용한다면 앱과 앱에서는 지마켓 내부의 여러 API들을 참조할 필요 없이 홈 모듈화 플랫폼만 참조하여 사용할 수 있어 facade API의 성격을 띤다고도 할 수 있습니다.
위의 두 이미지와 같이, 앱과 웹은 다른 플랫폼이지만 같은 지마켓 서비스이기에, 가급적이면 동일한 UI를 가져가야 합니다. 중간계층인 홈 모듈화 플랫폼이 없다고 가정하면, 앱과 웹에서 각각 서버에 있는 데이터를 각자의 방법대로 가져다 쓸 것이고, 이 간극이 벌어지게 된다면 서버 입장에서는 어떻게 응답을 줘야 할지 혼란이 오게 되어 시스템이 장황해지게 됩니다.
홈 모듈화 플랫폼에서는 백엔드 시스템에서 제공하는 데이터를 통합하여 템플릿 형태에 맞게 가공하여 앱과 웹에 동시에 전달하고 있으며, 앱과 웹에서는 이렇게 전달된 데이터를 받아 UI를 구현하면 됩니다. 중간 계층에 위치함으로써 응답을 표준화하고 있는 셈이죠.
facade가 아닌 다른 솔루션들
이 글의 주제가 facade API에 맞춰져 있어서 facade API 솔루션을 중심으로 설명해 드렸지만, 이 외에도 저희 팀은 다양한 솔루션을 개발하고 운영해 오고 있습니다.
3-scale api gateway, 다른 도메인 호출을 위한 사용자 인증 api, 토큰 암복호화 api, 대기열 시스템 등, 여러 솔루션을 담당하고 있습니다. 나중에 기회가 되면 이 솔루션들도 소개해 드리도록 하겠습니다.
Our Tech Stack
facade API가 무엇이고, 왜 사용해야 하며, 어떻게 조직에 적용시키는지까지 설명드렸다면, 구체적으로 어떤 기술 스택을 사용하여 구현하였는지 궁금하시리라 예상됩니다.
facade API는 여러 API를 호출해야 하므로 속도가 빨라야 하며, async와 non-blocking에 특화된 프레임워크를 주로 사용하고 있습니다. 저희 팀에서 운영하고 있는 facade API 시스템은 지마켓과 옥션 각각의 백엔드 시스템을 통합하여 facade API로 제공해 주고 있으며 TypeScript 기반의 Node.js와 Kotlin 기반의 Spring Webflux로 구성되어 운영되고 있습니다. 홈 모듈화 플랫폼은 Java 기반의 Spring Boot로 구성되어 있으며, 많은 양의 데이터를 호출하고 응답해 주기 위해 Spring Webflux 또한 사용하고 있습니다.
facade API는 백엔드 시스템의 데이터를 통합하여 클라이언트로 전달해 주는 역할을 하기에 직접 백엔드 데이터에 접근을 하지 않는다는 특징을 가지고 있습니다. 두 솔루션 모두 직접 데이터를 호출하는 일이 적기에 데이터를 사용할 목적으로 DB 접근을 하지 않아 DBMS 관련 사용 빈도는 다른 백엔드 개발 팀에 비해 낮은 편이라고 할 수 있습니다. 다만 더 빠른 응답을 제공하기 위해 데이터를 캐싱하여 제공하고 있으며, 캐싱을 위해 Redis를 사용하고 있습니다.
맺음말
이번 글에서는 facade API에 대한 간단한 소개와 이를 사용해야 하는 이유, 그리고 저희 팀에서 facade API를 어떻게 활용하고 적용하고 있는지와 사용한 기술 스택에 대해 살펴보았습니다.
facade API는 복잡한 시스템을 간단하게 만들어주는 효과적인 도구로, 잘 구현되었을 때 기술적인 부담을 크게 줄이고 생산성을 높일 수 있습니다. 하지만 facade API를 정의하고 관리하는 것은 쉬운 일이 아닙니다. 고려해야 할 사항들이 많으며, 조직 구성이나 기술 스택 등 여러 변수에 따라 구현의 복잡성이 달라질 수 있습니다.
이번 글을 통해 facade API에 대해 익숙하지 않으셨던 분들께는 facade API에 대한 기본 가이드라인이 되었으면 하며, facade API 도입을 고려하고 있는 다른 조직에서도 facade API를 도입하는데 참고가 되었으면 합니다.
부족할지도 모르는 긴 글 읽어주셔서 감사합니다. 다음에 더 좋은 글로 돌아오도록 하겠습니다!
기타
facade를 기술하는 방법에는 미국식 스펠링인 facade와 프랑스식 스펠링인 façade 두 가지 표기 방법이 존재했고, 어떻게 기술해야 할지 고민이 되었습니다.
https://en.wikipedia.org/wiki/Talk:Façade
https://english.stackexchange.com/questions/47792/facade-vs-façade
해당 링크에 따르면 facade의 사용 빈도가 façade의 사용 빈도보다 우세하여 facade를 채택하였습니다.
다른 기술 문서에서 façade라고 기술되어 있어도, 본 글에서 기술한 facade와 같은 개념으로 이해하셔도 무방하리라 생각됩니다.
Reference
퍼사드
퍼사드 패턴
- https://cloud.google.com/files/apigee/apigee-api-facade-pattern-ebook.pdf
- https://ko.wikipedia.org/wiki/퍼사드_패턴
의존관계 역전 원칙
- https://ko.wikipedia.org/wiki/의존관계_역전_원칙
- 클린 아키텍처 : 소프트웨어의 구조와 설계의 원칙 / 로버트 C.마틴 / 인사이트
'Culture' 카테고리의 다른 글
IT에 대한 짧은 단상 (1) | 2024.01.22 |
---|---|
막내 개발자의 Seller PD 생활 - v.2023 (0) | 2023.07.12 |
개발자의 글쓰기는 다르다. (1) | 2022.08.03 |
신입 iOS 개발자의 "치약 프로젝트" 회고 (2) | 2021.01.29 |
막내 개발자의 Sell POD PD 생활 (3) | 2020.11.17 |