- Published on
협업중에 api 설계 스타일에 대한 고찰
- Authors
- Name
- Yanguk
서론
필자는 서버 api 작업시 항시 REST
원칙을 지키는 스타일이다.
모든 원칙을 지킬순 없지만 적어도 아래 세가지 원칙은 지킬려고 하는 편이다.
1. 무상태성
- 서버가 클라이언트의 상태를 기억하지 않는다.
- 밑에 2번과 비슷하긴 한데
/posts?page=2&limit=10
이것 처럼 필요한 정보는 클라이언트에게 받는다.
2. URL로 리소스를 식별할수 있는가
- 예를 들어
/products/123
처럼 상품에 123번 상품인걸 알 수 있듯이... - URL은 행위가 아닌 리소스를 표현해야 하며, 동사 대신 명사 중심으로 설계해야 한다.
3. HTTP 메소드를 특정 목적에 맞게 사용하는가?
- GET 요청에서 db을 변화하는 행위를 하면 안되는 것 처럼 메소드에 맞게 행위를 지정한다.
- 이는 GET 요청은 클라이언트에서 캐싱을 수행할 수 가 있기에 중요한 요소라고 생각한다.
- 그리고 메소드의 멱등성 성격에따라 특정한 api는 연속 중복 호출을 막는 행위를 수행할 수가 있다.
그런데 협업중에 이 원칙을 지키지 않는 팀원분들도 많이 보았다.
코드에는 정답은 없기에 옮고 그름을 판단할 순 없지만 개인적으로 선호하는 방식은 RESTful
한 설계 방식이다.
그렇지만 현재 진행중인 프로젝트를 RESTful하게 설계 되어있지 않아서 이에 대한 생각을 정리해보고자 한다.
현재 RESTful 하지 못한 프로젝트 상황
사용된 기술 스택
- nextjs
- trpc
- prisma
- postgresql
도메인 설명
회사에서 진행하는 프로젝트 이기에 코드를 바로 넣을순 없고 도메인을 살짝 변경하여 다른 예시로 작성하였습니다.
현재 프로젝트는 3단계 뎁스 구조를 가지고 있는데, 이를 일반적인 온라인 학습 플랫폼 구조로 예시를 들면 다음과 같다
Course (코스) -> Lesson (레슨) -> Problem (문제)
그리고 뎁스는 모두 1:다
구조 이다. 하나의 코스에 여러가지 레슨이 있다.
현재 API 구조
기술스택이 trpc이기에 url과 http메소드가 아닌 query • mutation, 함수 메소드 명 으로 구분한다.
// 레슨 페이지에서 호출 (정보 조회 및 startedAt 업데이트)
mutation.startLesson({
courseId,
})
// 문제 풀이 페이지에서 호출 (정보 조회 및 startedAt 업데이트)
mutation.startProblem({
cornerId,
})
// 문제 풀이 페이지에서 풀이 제출
mutation.finishProblem({
cornerId
})
startLesson으로 예를 들자면
코스 밑에 여러가지 레슨이 있는데, 해당 레슨을 시작할때 호출하는 api 이다.
해당 api가 하는 기능은 다음과 같다.
- 학습에 필요한 레슨 정보 응답
- 서버에서 레슨 순서를 판단하여 진행해야할
Lesson row
생성
단점
- api의 정보만 봐서는 어떤 레슨을 시작하는지 모른다. 다른 레슨을 각각 시작할때마다 서버에서 엑세스로그는 항상 같은 courseId가 찍힐 것 이다. 디버깅 할려면 직접 로직을 타야만 알 수 가 있다.
- 하나의 api가 GET의 역할과 로우를 생성하는 POST 역할이 동시에 존재한다. 이에 따라 클라이언트는 항상 같은 응답값을 받아도 캐싱하기가 힘들다.
- 매번 서비스 로직에서 시작해야하는 problem과 lesson을 찾아야한다.
장점
- 클라이언트에서는 호출하는 api가 단순함
RESTful하게 수정한다고 하면...
query.getProblemInfo({ problemId }) // 문제 페이지에서 정보 조회
mutation.startProblem({ problemId }) // 시작시간을 기록하기 위함
mutation.finishProblem({ problemId }) // 문제 풀이 제출
// 레슨 페이지 에서 호출 (학습에 필요한 레슨정보 조회)
query.getLessonInfo({
lessonId,
})
// 레슨 페이지 에서 호출 (startedAt 업데이트)
mutation.startLesson({
lessonId,
})
장점
- input값 만 보고 어느 레슨에 대한 조회 or 업데이트인지 알 수 있음
- info 조회부분은 변하지 않기에 조회부분은 캐싱이 가능함
단점
- 클라어언트에서는 여러개의 api를 다뤄야한다.
- 해당 problem이나 lesson의 row를 미리 생성해놔야한다.
결론
현재 구조는 RESTful하지 않게 설계 되어있다. 그 이유는 아래와 같다.
- nextjs에서 trpc를 사용하기에 REST API가 아니라서 지킬 필요가 없다는 주장
- 클라이언트에서 처리하는 일을 줄이기 위함
- row를 미리 생성해놓을 타이밍을 잡기가 까다로움
내 생각
현재 RESTFul
하지 않는 구조로 개발하면서 개인적으론 이게 자연스러운 흐름인가 맞나 싶기도 하고, 디버깅 및 테스트코드 작성하기가 까다롭다는 느낌을 받았다. 그리고 nextjs의 앱 라우팅방식과 컨셉에 맞지 않는 방식이라고 생각한다.
row를 미리 생성하기 까다로운 것도 이유 중 하나인데 잘 이해가 가지 않는다.
course나 lesson, problem을 시작하기 위해서는 mutation이 발생할 수밖에 없는데 그 시점에 row를 생성하고 id를 응답해 주면 클라이언트에선 그 id로 라우팅과 api를 호출하는 게 자연스러운 흐름이 아닌가?
나의 컴퓨팅 구조적사고가 고정관념에 박힌건지 모르겠으나 아마 내가 미처 눈치 채지 못한 이유들이 분명히 있을 것이다. 모든건 정답이 없고 장단점만 존재할 뿐이니...
그리고 GraphQL
도 REST
와는 다른 접근 방식을 취하는 것처럼 어쩌면 REST 원칙에 얽매이기보다는 각각 처한 환경에 맞는 설계 방식을 택하는 것이 더 현명한 선택일 수도 있겠다는 생각이 든다.