[Architecture] API는 어떻게 설계해야 할까
0. 서론
팀 프로젝트를 하면서 다음과 같은 상황을 마주했다.
나의 모멘트(게시글) 페이지를 불러올 때, 해당 모멘트에 알림이 달렸는지 여부도 함께 불러와야 했다.
처음엔 기존 특정 유저의 모멘트들을 불러오는 API가 이미 존재하니, 이걸 건드리지 않고 특정 유저의 알림 정보를 불러와서 프론트에서 조합하게 만든다면 깔끔하게 해결된다고 생각했다. 그래서 따로 만들었다.
만들고 몇 주 뒤, 프론트 측에서 다음과 같은 요구사항이 들어왔다.
모멘트와 알림을 프론트에서 조합하는건 비효율적인 것 같다. API 요청을 2번 보내야 하니 응답 시간 면에서도 손해다. 서버 측에서 하나로 조합해 보내주는것은 어떤가?
듣고 보니 맞는 말 같았다. 어딘가 찜찜했지만 명확한 개선 포인트가 있으니 거절할 수 없었다.
1. 왜 처음엔 2번 요청을 보내도록 설계했는가?
가장 먼저 생각한 것은
“기존 API를 그대로 유지하면서 신기능을 넣고 싶다.”
였다.
자연스럽게 OCP를 지키면서도, 레고처럼 조합 가능한 API를 설계하고 싶었다. 당장은 생각 안 나지만, 내가 만든 API가 어딘가에서 쓰일 수도 있으니까?
그러려면 최대한 기능의 범위를 작게 만들어야 했다. 기능의 영향 범위도 작아져, 쉽게 디버깅이 가능한 것도 장점이다. 딱 내가 원하는 API 설계대로 이루어졌다고 생각했다.
하지만 이 과정에서 성능 문제를 생각하지 못했다.
2. 요구 사항대로 설계한다면?
좋다! 성능 중요하지. 응답 속도도 개선하고, 불필요한 트랜잭션과 스레드 점유도 줄일 수 있으니 일석이조 라고 생각했다.
따라서 다음과 같은 방향으로 변경했다. (어느 정도 간략화해서 설명한다.)
- 모멘트와 알림 Repository에서 엔티티들을 불러온다.
- Facade 레벨에서 새로운 응답 DTO를 만들어 각각의 엔티티를 결합한다.
- 컨트롤러에서 내보낸다.
해당 방식으로 리팩터링한 후 몇 가지 문제점을 느꼈다.
기존 API 스펙의 변경: 이 방식은 어쩔 수 없이 기존 API 응답이 바뀌어야 했다. 현업이라면 아무래도 일어나기 어려운 일이라고 생각했다.
물론 우리 서비스는 규모도 작고, 초기라 이정도는 감안할 수 있는 부분이라 생각했다.
- 지루한 매핑 코드의 증가: 지루하고 현학적인 매핑 과정이 Service 코드의 많은 부분을 차지했다. 해당 코드를 DTO로 분리하긴 했지만, 여전히 보기 힘들었다.
- API의 혼란: API가 기능을 완전히 표현하지 못한다는 생각이 들었다.기존과 API endpoint는 동일하지만 기능은 변경됐다. 하지만 알림이 추가됐다는 것을 어떻게 표현하지? 무릇 REST 라면 제대로 Represent 해야 하는거 아닌가?
- View 종속성: 가장 고민되었던 부분은 이렇게 만들수록 API가 View에 종속되는 방향으로 흘러갔다는 것이다. 내가 최초에 생각하던 작은 API, 레고 like API, REST와는 다른 방향 같았다.
3. Server의 사용자는 누구인가?
내가 고민하지 못한 것이 바로 이 부분이다. Server의 API를 누가 사용하는가?
결국 API는 Interface고, Server와 Client 사이에 위치한다. 두 계층 사이의 계약이고 Server 입장에선 API를 설계할 때, Client가 누구보다 중요하다.
우리의 API는 공공 API가 아니다. 누구든지 가져다 써서, 갖가지 새로운 애플리케이션으로 태어날 가능성이 있는 API가 아니라는 것이다. 우리의 클라이언트는 반드시 우리 프론트엔드 팀 뿐이다. 따라서 API가 우리 View에 맞춰져 있어도 크게 문제가 없다는 결론을 내렸다.
그럼 고민들은 어떻게 하지? 작은 API, 레고 like API 등은 어떻게 실현해야 할까?
우리는 이 고민을 API보다 한 계층 아래에서 생각하기로 했다. 최대한 도메인을 분리하고, Service 레이어는 반드시 해당 도메인에 대한 기능만 처리하도록 분리했다.
그 후, 필요한 기능들은 Facade 레벨에서 조립했다. 이러면 View 레벨에서의 기능 변경이 Facade 아래로 전파되지 않게 할 수 있다.
성능에 대한 고민에서부터 시작한 궁금증이, 전체 아키텍쳐를 갈아 엎는 결과가 됐다. (물론 이 이유가 100%는 아니다. 어쩌다가 맞아 떨어졌을 뿐…)
