HTTP 요청을 처리하면서 URL path와 method에 따라 다른 동작을 실행해야 하는 경우는 백엔드 개발의 가장 기본적인 문제다.
처음에는 조건문으로 시작했지만, API가 많아질수록 유지보수성과 확장성에 문제가 있을거 같았다.
이에, 전략 패턴 기반으로 분기 처리하게 했다.
Dispatcher: 요청을 받아 해당 도메인의 AppServlet으로 위임AppServlet: 도메인별 진입점 AppFacade 의 요청별 메소드 호출AppFacade: 요청 경로와 method에 따라 처리할 BiFunction 전략을 보유RouteKey: (Path, Method) 조합을 키로 사용routeMap: Map<RouteKey, Function<HttpRequest, HttpResponse>>Function: 각 API 요청을 처리할 함수 전략요청 -> Dispatcher -> AppServlet -> AppFacade -> routeMap -> Function 실행 -> 응답 생성
OCP를 만족하는 확장성 있는 구조
비즈니스 로직과 라우팅을 분리
함수형 인터페이스의 장점 활용
장점: 간단하고 빠르게 구현 가능
단점:
API가 많아질수록 분기문이 비대해지고 가독성이 떨어진다.
확장성과 재사용성, 테스트 용이성이 부족하다.
다양한 Handler 타입(JSON, HTML, 파일 응답 등)을 유연하게 처리 가능
Dispatcher는 Handler의 supports()와 handle()만 호출하면 되므로 구조적으로 매우 깔끔함
새로운 핸들러가 생겨도 기존 Dispatcher 수정 없이 어댑터만 추가하면 됨.
Spring은 @RequestMapping, 등의 어노테이션을 통해 요청 경로와 메서드를 선언적으로 정의할 수 있도록 지원한다.
라우팅 정보가 명시적으로 드러나고, 자동 등록 가능
외부에서 라우팅을 정의하므로 유지보수와 자동화에 유리
Reflection, ComponentScan 을 공부해보자.
현재 구조는 Function<HttpRequest, HttpResponse>에 의존하고 있어 비즈니스 로직에서 다루는 데이터와 HTTP 메타정보가 섞여 있는 상태다.
예를 들어 회원가입을 처리하는 로직이 HttpRequest에서 직접 query param을 꺼내는 방식은
이런 문제가 있다.
현재는 Function<HttpRequest, HttpResponse> 으로 분기 처리를 수행하고 있다.
등을 만들고 싶다면 HandlerAdapter 추상화를 도입해야 한다.
Dispatcher는 어떤 핸들러든 supports()가 true인 Adapter만 찾으면 되고, handle()은 어댑터가 책임지므로 다형성이 생긴다.
Spring은 많은 개발자들이 겪는 반복적이고 확장에 취약한 구조들을 해결하기 위해, 구조적 분리와 추상화, 그리고 유연한 어댑터 패턴을 도입해싿.
Function 기반 전략 패턴도 충분히 학습 가치가 있었지만, Spring의 설계 철학을 느끼게 되었다.