티스토리 뷰
배경
러닝 관련 API를 구현하던 중 러닝을 기록하는 API를 설계했었습니다. 이 과정에서 러닝 상태 전이에 따라 러닝 시작, 중단, 재개, 완료 등의 기능을 구현해야했습니다. 그리고 각 API를 어떻게 설계하는게 좋을지에 대한 고민으로 이 문서를 작성하게 되었습니다.
필요한 API는 다음과 같습니다.
- 러닝 시작 API
- 러닝 기록 업데이트 API
- 러닝 중단 API
- 러닝 재개 API
- 러닝 완료 API
고민
크게 고려하고 있는 방식은 두 가지입니다.
방식 1. 액션별 엔드포인트 + DTO로 recordId 전달
기본적으로 /api/v1/running 엔드포인트에 이후 액션을 엔드포인트로 사용하는 방식입니다.
@RestController
@RequestMapping("/api/v1/running")
class RunningController(
private val runningService: RunningService,
) {
@PostMapping("/start")
fun start(@RequestBody req: RunningStartRequest): ApiResponse<...> = …
@PostMapping("/update")
fun update(@RequestBody req: RunningUpdateRequest): ApiResponse<...> = …
@PatchMapping("/stop")
fun stop(@RequestBody req: RunningStopRequest): ApiResponse<...> = …
@PostMapping("/resume")
fun resume(@RequestBody req: RunningResumeRequest): ApiResponse<...> = …
@PostMapping("/done")
fun done(@RequestBody req: RunningDoneRequest): ApiResponse<...> = …
}
위 예시를 통해 알 수 있듯이 모든 행위가 엔드포인트로 붙었습니다.
- /api/v1/running/start
- /api/v1/running/update
- /api/v1/running/stop
- …
방식 2. 자원(Resource) + 액션별 하위 경로
자원을 URI에 나타내고 HTTP 메서드로 행위를 나타내는 RESTful한 방식입니다.
@RestController
@RequestMapping("/api/v1/running")
class RunningRecordController(
private val runningService: RunningService,
) {
@PostMapping()
fun start(@RequestBody req: RunningStartRequest): ApiResponse<...> = …
@PostMapping("/{recordId}/points")
fun addPoint(@PathVariable recordId: Long,
@RequestBody point: RunningPointDto): ApiResponse<...> = …
@PatchMapping("/{recordId}/stop")
fun stop(@PathVariable recordId: Long): ApiResponse<...> = …
@PatchMapping("/{recordId}/resume")
fun resume(@PathVariable recordId: Long,
@RequestBody req: ResumeRequest): ApiResponse<...> = …
@PatchMapping("/{recordId}/done")
fun complete(@PathVariable recordId: Long,
@RequestBody req: CompleteRequest): ApiResponse<...> = …
}
아마 RESTful API에 익숙한 개발자는 기본적으로 생각하는 방식은 위 방식을 채택할 것 같습니다.
저는 방식 1을 선택했었습니다.
이 방식1 은 URI는 자원으로 나타내고 HTTP 메서드를 통해 행위를 나타내는 RESTful API의 원칙에 일치하지 않습니다. 행위를 URI를 통해 나타내고 있고 HTTP 메서드를 통해 작업을 구분하지 않기 때문입니다. 그렇기에 대부분의 개발자는 방식 2를 선택하실 것 같습니다.
그럼에도 제가 방식 1을 선택한 이유는 다음과 같습니다.
[이유 1. RESTful 하게 나타내는 것의 한계점이 있다.]
RESTful 하게 API를 설계하기 위해선 자원을 통해 URI를 나타내고 HTTP 메서드를 통해 행위를 정의해야합니다. 하지만 저는 이 방식의 한계점이 존재하다고 생각했습니다.
이러한 한계점은 위 방식 2의 예시를 통해서도 확인할 수 있습니다. 러닝을 시작하고 기록을 추가하는 API는 자원과 메서드를 통해 행동을 정의할 수 있습니다.
- 러닝 시작: POST /api/v1/running
- 러닝 기록 추가: POST /api/v1/running/{runningId}
하지만 그 외의 행위에 대해선 URI에 자원을 나타내기에도, HTTP 메서드로 행동을 정의하기에도 한계점이 있습니다.
- 러닝 중단: PATCH /api/v1/running/{runningId}/stop
- 러닝 재개: PATCH /api/v1/running/{runningId}/resume
- 러닝 완료: PATCH /api/v1/running/{runningId}/done
URI에 ‘행위’가 포함되고 HTTP 메서드로 행위를 구분하지 않죠. 이러한 한계점은 실제로 종종 경험하게 됩니다. 모든 API가 자원 + HTTP 메서드로 깔끔하게 나타낼 수 없는 것을 종종 경험하셨을 겁니다. 저 또한 이런 경험이 있었고 위 API를 설계했을 때도 동일하게 경험했습니다.
[이유 2. Depth를 줄일 수 있다]
방식 1을 선택하게 되면 방식 2에 비해 Depth를 하나 더 줄일 수 있습니다.
러닝 기록 중단 API를 예시로 살펴봅시다.
- 방식 1 : POST /api/v1/running/stop
- 방식 2 : PATCH /api/v1/running/{runningId}/stop
방식 1의 경우 러닝 기록과 관련된 API가 동일한 Depth를 사용하게 됩니다. 따라서 가시성에도 좋고 라우팅 로직이 단순합니다. 또한 방식 1의 경우 모든 정보를 DTO로 관리하기에 runningId 를 URI에 포함하는 방식 2의 비해 통일성도 있다고 생각했습니다.
다음과 같은 이유로 저는 방식 1을 선택했습니다.
방식 2로 리팩터링하려고 합니다.
지금은 방식 1을 선택하긴 했지만 어떤게 더 나은 방식인지에 대해 계속 고민하게 되었습니다. 지금 단계에서는 API가 복잡하지 않기에 방식 1이 더 간단하게 느껴지지만, 만약 행위가 더 늘어나게 된다면 동일한 Depth를 유지하는 방식이 과연 유지보수에 좋을지는 의문입니다. (Depth를 유지하는게 큰 장점인지도 느껴지지 않았습니다)
그리고 RESTful 하게 나타낼 수 있는 자원도 RESTful하지 않게 표현하게 된다는 점이 단점으로 느껴집니다. 한계점이 있다고 장점을 모두 포기하는것이 과연 맞을지 의문이죠.
그래서 방식 2로 리팩터링하고자 합니다. 방식 1의 장점이 크게 느껴지지 않는다면 대부분의 개발자가 익숙한 RESTful 방식을 선택하는게 추후 유지보수에서 장점을 챙길 수 있죠.
만약 방식 2로 개선한 이후 단점이 있다면 또 글로 남겨보겠습니다!
'개발 스토리' 카테고리의 다른 글
| Google Cloud Bucket에 저장된 음성 파일 가져오기 (0) | 2025.07.01 |
|---|---|
| logQL 파싱하여 로그에서 메타 데이터 추출하기 (3) | 2025.06.27 |
| 기술 블로그 아카이빙 서비스 - TechLog (5) | 2025.06.02 |
| 레디스 Stream으로 분산 서버에서 푸시 알림 중복 전송 문제 해결하기 (0) | 2025.04.11 |
| 코루틴 기반 Watchdog으로 분산 서버에서 푸시 알림 중복 전송 문제 해결 (0) | 2025.04.08 |
- Total
- Today
- Yesterday
- redis
- 게임개발
- 코루틴
- 6기
- Assertions
- Cache Stampede
- 자바
- 토스 2025 NEXT
- 우테코 준비
- API 지연
- 토스 합격 후기
- 토스 백앤드 합격
- 우아한테크코스 6기
- 우테코
- 레디스
- 우테코 프리코스
- 토큰
- 우아한테크코스 후기
- 커넥션 데드락
- JWT
- 분산락
- stoplight
- 우아한테크코스
- 파이썬
- 캐시 스템피드
- 토스 next 2025
- 알고리즘
- 우아한테크코스 자소서
- 우테코 6기
- 토스 NEXT 후기
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |