들어가며
- 이전의 아이디어를 적용함으로서 꽤나 근사한 MVC 프레임워크를 만들 수 있었다.
- 개발자는 컨트롤러를 만들 때 단순히 뷰의 논리적 이름만을 반환하도록 하면 끝나는 매우 편리한 프레임워크가 되었다.
- 하지만 다른 개발자가 어떠한 사정으로 인하여 이전 버전의 컨트롤러들을 사용해야 한다고 한다. 이런 상황이 온다면 어떻게 해야할까? 이번 포스트에서는 해당 문제에 대해 생각해보고 해결 방법을 알아 본다.
- 결과 코드는 여기에서 볼 수 있다.
확장성 - 다른 버전의 컨트롤러를 사용해야하는 문제
- 모든 개발자들이 모두 동일한 버전의 컨트롤러를 사용하기는 힘들다. 특정한 사정이나 어떠한 문제를 해결하는데 이전 버전의 컨트롤러나 다른 형태를 가지는 컨트롤러들을 사용해야 할 수도 있다.
- 하지만 현재 전체적인 처리를 맡고 있는 프론트 컨트롤러는 그것이 잘 안되는데 코드로 보도록 하자.
@WebServlet(name = "frontControllerServletV4", urlPatterns = "/front-controller/v4/*")
public class FrontControllerServletV4 extends HttpServlet {
private final Map<String, ControllerV4> controllerMap = new HashMap<>();
public FrontControllerServletV4(){
controllerMap.put("/front-controller/v4/members/new-form", new MemberFormControllerV4());
controllerMap.put("/front-controller/v4/members/save", new MemberSaveControllerV4());
controllerMap.put("/front-controller/v4/members", new MemberListControllerV4());
}
...
}
- 매핑 정보를 가지고 있는 Map에 ControllerV4로 명시가 되어 있기 때문에 다른 타입의 컨트롤러를 사용하고 싶어도 매핑에 문제가 생겨버린다.
- 퍼즐을 생각해보면 다른 모양의 퍼즐을 끼울 수 없기 때문에 controllerMap이라는 퍼즐에 ControllerV4 이외의 다른 유형의 퍼즐을 끼울 수 없다는 것이다.
- 그렇다면 확장을 할 수 없는 것일까?
Adapter 개념과 HandlerAdapter
- 이런 문제를 해결하기 위해서 어댑터라는 개념이 등장한다. 어댑터는 말 그대로 우리가 외국에서 110V를 사용하는 콘센트를 사용해야할 때 220V 어댑터를 끼우는 것 처럼 다른 타입을 다룰 수 있게 해준다.
- 무언가 추상적으로 느껴진다. 조금 더 생각해보도록 하자.
- 기존의 구조를 생각해보자. 기존에는 프론트 컨트롤러가 직접 컨트롤러를 호출하는 형식을 사용하였다. 때문에 특정 유형의 컨트롤러만을 가질 수 있었다.
- 그럼 이를 한 단계 더 나아가보자. 프론트 컨트롤러가 직접 컨트롤러를 호출하는 형식이 아니라 다른 어떤 객체에게 컨트롤러를 호출하는 책임을 위임하는 것이다.
- Something이라는 객체는 컨트롤러를 호출하는 책임을 가진다. 이 이름을 HandlerAdapter라고 하자.
- 이 객체에 다형성을 적용시켜 보자. 즉 우리가 버전 3, 버전 4의 컨트롤러들을 가지고 있으므로 버전 3의 컨트롤러를 호출할 수 있는 어떤 객체도 존재할 수 있고, 버전 4의 컨트롤러를 호출할 수 있는 어떤 객체도 존재할 수 있다.
- 그렇다면 이 HandlerAdapter은 어떤 버전의 컨트롤러를 호출 할 수 있는 인터페이스가 되어야 할 것이다. 인터페이스로 추상화 시킨다면 다음의 문제가 자연스럽게 도출된다.
HandlerAdapter의 구현체가 자신이 다룰 수 있는 타입과 맞는 컨트롤러인지를 어떻게 확인할 수 있는가
- 이를 해결하는 가장 쉬운 방법은 호출해야할 컨트롤러의 버전을 확인하면 된다. 각 컨트롤러들은 자신의 컨트롤러 버전을 나타내는 인터페이스들을 구현한 구현체이기 때문에 instance of 연산을 통하여 컨트롤러의 버전을 확인할 수 있다.
- 이를 구현하는 것은 쉽다. HandlerAdapter 인터페이스를 구현하는 구현체가 instance of 연산을 통하여 현재 프론트 컨트롤러가 호출해야하는 컨트롤러가 어떤 버전의 구현체인지를 확인하도록 하면 된다.
- 이를 위해서는 각 컨트롤러를 호출할 수 있는 HandlerAdapter 인터페이스의 구현체들이 모여있는 집합이 필요하다.
- HandlerAdapter의 구현체들이 모인 리스트에서 현재 호출해야할 컨트롤러를 호출할 수 있는(handle 할 수 있는) HandlerAdapter 구현체를 찾고, 해당 구현체를 통해 컨트롤러를 호출하게 함으로서 컨트롤러가 어떤 버전이라도 호출할 수 있게 되었다.
- 만약 컨트롤러가 버전 3이라면 버전 3에 맞는 HandlerAdapter 구현체를 만들면 된다.
- 만약 컨트롤러가 버전 4라면 버전 4의 맞는 HandlerAdapter 구현체를 만들면 된다.
- 각기 다른 컨트롤러들을 호출(handle)할 수 있는 HandlerAdapter를 구현함으로서 확장성을 가지게 되었다.
- 그리고 컨트롤러도 약간의 의미적 변화가 생기게 되는데 바로 핸들러 라는 개념으로 변한다는 것이다. 핸들러는 어떤 요청을 처리할 수 있다는, 컨트롤러에 비해 더 넓은 의미로 사용되는데 이렇게 될 수 있는 이유는 어댑터 때문이다.
- 이제 컨트롤러 뿐 아니라 어떠한 것이더라도 해당하는 종류의 어댑터만 존재한다면 이 어댑터를 통하여 요청들을 모두 처리할 수 있기 때문이다.
HandlerAdapter에 요구되는 것과 구현
- HandlerAdapter는 다음과 같은 책임을 가진다.
- 넘어오는 핸들러(컨트롤러) 객체가 자신이 호출할 수 있는 객체인지 확인하는 책임
- 해당 핸들러(컨트롤러) 객체를 호출하고 그 결과를 프론트 컨트롤러에 반환하는 책임
- 자신이 호출 가능한지 확인하는 책임은 자신이 호출이 가능한지(true) 불가능한지(false)를 반환할 수 있으면 된다.
- 핸들러 객체를 호출하고 그 결과를 프론트 컨트롤러에 반환하는 책임은 그 결과를 어떤 형태로 프론트 컨트롤러에 반환해야 한다는 의미이다. 이는 우리가 이전에 만들었던 ModelView를 사용하여 반환하도록 하자.
- 이것에 대한 이유는 다음 포스트에서 설명하도록 하겠다.
- 그렇다면 HandlerAdapter 인터페이스는 다음과 같게 될 것이다.
public interface MyHandlerAdapter {
boolean supports(Object handler);
ModelView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException, IOException;
}
- request, response는 프론트 컨트롤러에서 받은 요청을 MyHandlerAdapter의 구현체로 위임하기 때문에 필요하다.
- handler는 처리해야 할 핸들러(컨트롤러) 인데 구현체에서 해당 핸들러를 처리할 수 있는지를 확인해야 하기 때문에 최상위 부모인 Object로 가져오게 하였다.
- handle에서는 해당 handler를 형 변환을 통해 각 버전에 맞는 컨트롤러 인터페이스로 변경시켜 사용하게 될 것이다.
정리
- 지금까지 확장성을 위해 HandlerAdapter라는 개념을 유도하고 이 HandlerAdapter가 가져야 하는 책임들에 대해 알아보고 구현해보았다.
- 다음 포스트에서는 이 HandlerAdapter를 구현한 구현체들이 어떻게 되어 있는가를 보고 전체적인 프론트 컨트롤러의 모습을 알아보도록 한다.
'Spring & JPA > SpringMVC' 카테고리의 다른 글
Spring MVC - MVC 구조 - 9. 프레임워크 구조 정리 (0) | 2023.06.02 |
---|---|
Spring MVC - MVC 구조 - 8. 확장성 적용(2) (0) | 2023.06.02 |
Spring MVC - MVC 구조 - 6. 개선된 MVC 구조 (0) | 2023.06.01 |
Spring MVC - MVC 구조 - 5. Model의 추가 (0) | 2023.06.01 |
Spring MVC - MVC 구조 - 4. View의 분리 (1) | 2023.05.31 |