여러분은 MVC 패턴에 대해서 얼마나 알고 계신가요? 단순히 먼저 접한 Model, View, Controller의 역할과 단순한 구현 방법에 대해서만 알고 계신가요?
목차
해당 글은 우테코 5기 프리코스의 미션 중 다리 건너기 미션을 진행하면서 생긴 의문점을 해결하기 위해 자료를 찾아보고, 공부한 과정을 정리 및 공유하기 위한 글입니다. 혹여나 잘못된 정보는 댓글로 남겨주시면 감사하겠습니다.
우테코 5기 프리코스 4주 차 미션의 다리 건너기 미션을 수행하는데, 미션을 위해 주어진 클래스를 보아하니 뭔가 MVC 패턴을 사용하도록 유도하는 듯한 느낌을 받았습니다. 저는 이전에 Spring으로 개발을 해본 적이 있어서 MVC 패턴을 접했었고, 제가 알고 있는 MVC 패턴으로 개발을 진행했습니다.
하지만 미션 중 프리코스 슬랙에 어떤 분이 테코 톡의 MVC 발표 영상을 공유해주었고, 제가 알고 있던 MVC 패턴과 상이하다는 것을 알게 되었습니다.
해당 테코 톡에서 제가 알고 있는 MVC 패턴과 달랐던 점은 View가 Model을 의존하고 있던 점이었습니다. 제가 생각하는 View가 Model을 의존하면 안 좋은 점은 View에서 Model 객체를 다룰 수 있는 여지를 남겨놓는 것입니다. View에서 Model객체의 데이터를 getter로 가져와서 출력만 한다고 해도, View에 Model 객체를 의존하고 있기 때문에 View에서 Model 객체를 다룰 수 있는 여지가 있습니다.
따라서 저는 구글링을 해보았고, 여러 글들에서 각각 상이한 MVC 패턴을 설명하고 있었습니다. 찾아본 결과 MVC 패턴은 워낙 오래전부터 생겨난 개발 방법론(아키텍쳐)이고, 시대를 거듭할수록 모바일에서, 웹에서 적용되면서 변화해왔던 것을 알 수 있었습니다.
MVC의 역사에 대해 공부하다 보니 MVC -> MVP -> MVVM 으로 변화해왔고, 자바 웹 진영에서는 서블릿, JSP, MVC v1, v2... 프레임워크로 변화해왔다는 것을 공부하게 되었습니다. 많은 사람들이 MVC 패턴이라고 부르면서 사용해온 것들이 생각만큼 표준화되어있지 않다고 생각했습니다.
저는 그래도 웹이나 안드로이드로 개발해본 적이 있어서 자료들을 찾아보고 이해하는데 어려움이 없었지만, 웹, 모바일 개발 경험이 없는 분들을 MVC 패턴 변화 과정이 어려울 수 있겠다고 생각했습니다.
따라서 공부한 점을 토대로 웹, DB, Servelet, 모바일 등 프레임워크 없이 기본 Java 프로그램만으로 MVC 패턴을 구현해보면서 상황을 부여하여 각 상황에서의 단점(아쉬운 점)을 보완하면서 MVC 패턴을 업그레이드시키면서 놀아보았고, 공부 과정을 공유하기 위해서 글을 정리해 보았습니다.
프로그램 예제는 우리에게 익숙한 우테코 프리코스 3주 차 미션의 로또 게임 미션을 간략화했습니다.
들어가기 전에,
3가지 핵심 기능이 있는 아주 간단한 프로그램입니다.
-----------------
1. 로또 구입
2. 구입한 로또 개수 조회
3. 구입한 로또 조회
4. 종료
번호를 입력해 주세요:
1
구입할 로또 번호를 입력 해주세요:
1,2,3,4,5,6
로또를 구입했습니다.
[1, 2, 3, 4, 5, 6]
-----------------
1. 로또 구입
2. 구입한 로또 개수 조회
3. 구입한 로또 조회
4. 종료
번호를 입력해 주세요:
1
구입할 로또 번호를 입력 해주세요:
2,3,4,5,6,7
로또를 구입했습니다.
[2, 3, 4, 5, 6, 7]
-----------------
1. 로또 구입
2. 구입한 로또 개수 조회
3. 구입한 로또 조회
4. 종료
번호를 입력해 주세요:
2
구입한 로또의 개수는 2장 입니다.
-----------------
1. 로또 구입
2. 구입한 로또 개수 조회
3. 구입한 로또 조회
4. 종료
번호를 입력해 주세요:
3
구입한 로또 목록입니다.
[1, 2, 3, 4, 5, 6]
[2, 3, 4, 5, 6, 7]
-----------------
1. 로또 구입
2. 구입한 로또 개수 조회
3. 구입한 로또 조회
4. 종료
번호를 입력해 주세요:
4
프로그램을 종료합니다.
앞으로 모든 예제에서 쓰이고 바뀌지 않을 Lotto 클래스와 Lotto들을 저장할 수 있는 LottoRepository를 만들었습니다. 우리의 예제는 MVC 패턴 변화가 핵심이기 때문에 Controller, View만 변화해나갑니다. Service 계층도 두지 않았습니다.
로또를 구입하는 기능인 LottoBuyJsp 클래스만 살펴보도록 하겠습니다.
MVC 패턴을 적용하기 이전의 코드입니다. Java 코드로 최대한 Jsp와 유사하게 짜보았습니다.(Jsp를 모르시는 분들은 Java로 웹 개발을 이렇게도 했었구나 하고 넘어가시면 됩니다.)
우테코 프리코스를 다 마치신 분들이라면 해당 코드에서 문제점을 쉽게 발견할 수 있을 거라고 생각합니다.
문제점
해당 코드를 가장 기본적인 MVC 구조로 개선해 보겠습니다.
사용자 입력과 출력 부분을 InputView와 OuputView를 각각의 클래스로 분류하였습니다.
Controller는 InputView를 통해 요청을 받아서 비지니스 로직을 실행하고 프로그램 실행 흐름을 담당합니다. 그리고 결과를 OutputView에 전달해 결과를 출력합니다.
해당 MVC 패턴은 개요에서 언급했던 테코 톡에 나와있는 MVC 패턴과 동일합니다. 가장 기본적인 MVC 패턴으로 많은 분들이 쉽게 접할 수 있는 패턴입니다.
+ Controller에 비지니스 로직을 둘 수도 있지만, Controller가 너무 많은 역할을 담당합니다. 따라서 비지니스 로직은 서비스(Service)라는 계층을 두어서 처리할 수도 있습니다. (앞서 언급했듯이 MVC 패턴에 집중할 것이기 때문에 예제를 최대한 간단히 하였고, 서비스 계층은 고려하지 않았습니다.)
개선한 점
아쉬운 점(서비스가 지속해서 확장함을 가정합니다.)
Contoller와 OutputView를 확장하기 좋게 개선해보겠습니다.
각 사용자 요청마다 기능을 수행할 수 있는 Controller를 분리하고, MainController를 두어서 한곳에 집중되어 있는 기존의 Controller의 책임을 분리했습니다.
OutputView 역시 각 요청의 응답에 맞는 View를 따로 분리했습니다. 따라서 LottoBuyController에서 해당 LottoBuyOutputView를 이용하여 랜더링합니다.
개선한 점
아쉬운 점(서비스가 지속해서 확장함을 가정합니다.)
이러한 아쉬운 점을 개선해보도록 하겠습니다.
Controller를 ControllableV2 인터페이스로 추상화하여 모든 Controller는 해당 인터페이스를 implement 해야 합니다. 사용자의 모든 요청은 FrontController가 먼저 요청을 받도록 하고, 사용자 요청에 따라 해당 요청을 수행할 수 있는 Controller를 찾아서 Controller를 실행시켜 주도록 했습니다. Controller들은 Map 저장해 두고 사용자 요청은 1,2,3,4의 입력 번호에 따라 달라지기 때문에 1,2,3,4를 키로 잡았습니다.
이전의 OutputView에서 Domain Model을 직접 의존하던 부분을 Map을 Model로 이용하여 key, value 방식으로 Controller에서 OutputView로 필요한 데이터들을 담아서 꺼내 쓸 수 있도록 개선했습니다.
개선한 점
아쉬운 점(서비스가 지속해서 확장함을 가정합니다.)
해당 아쉬운 점을 개선해보겠습니다.
OutputView도 Viewable 인터페이스를 이용하여 추상화했습니다. 따라서 모든 OutputView는 해당 인터페이스를 implements 해야 하고, OutputView들을 OutPutViewFactory에서 논리 이름으로 가져올 수 있도록 했습니다.
(View를 가져오는 방법은 Map을 사용하는 것이 아니라 자바 reflection을 이용하여 패키지 명과 클래스 명으로도 가져오도록 하는 방법도 있을 것 같습니다.)
이전의 Controller 인터페이스와 다른 점은 ViewModel을 반환하는 부분입니다. ViewModel은 해당 Controller가 출력할 때 필요한 View를 찾는 논리 이름(경로)을 지정해주어 View를 찾을 수 있습니다. 또한 View에서 필요한 Model을 담을 수도 있습니다.
이후 FrontController에서 해당 Controller에서 반환받은 ViewModel 객체를 이용하여 랜더링 합니다.
개선한 점
아쉬운 점(서비스가 지속해서 확장함을 가정합니다.)
해당 아쉬운 점을 개선해보겠습니다.
이전의 Controller 인터페이스와 다른 점은 Model을 파라미터로 주입받아서 해당 Model을 이용하여 View에 전달할 데이터를 넣어주면 됩니다. 그리고 View의 논리 이름을 단지 반환하면 됩니다. Model은 어디서 주입해주는 걸까요?
FrontController에서 해당 Controller를 실행시켜주는 과정에서 Model을 주입시켜줍니다. 실행 결과로 viewPath를 받게 되고, 이후 기존에 사용했던 ViewModel을 생성하여 View를 랜더링 합니다.
개선한 점
아쉬운 점(서비스가 지속해서 확장함을 가정합니다.)
해당 아쉬운 점을 개선해보겠습니다.
일단 앞선 버전의 아쉬운 점인 필요 없을 경우 Model을 받지 않고 View의 논리 이름만 반환하는 ControllerV5를 빠르게 만들어 보겠습니다.
기존에는 로또를 구입하는 기능의 Controller를 예시로 들었지만, View로 Model을 전달할 필요가 없고, 단지 게임 종료 문구만 출력하는 즉, View 논리 이름만을 반환하는 LottoEndController를 예시로 들었습니다.
하지만 ControllerV5를 사용할 경우 View로 Model을 전달할 필요가 있을 경우 전달할 방법이 없기 때문에 ControllerV5만 사용할 수 없습니다.
따라서 FrontController에서 여러 버전(V3, V4, V5)의 Controller를 사용할 수 있도록 개선해 보겠습니다. 여러 가지 방법이 있겠지만, 디자인 패턴 중 어댑터 패턴을 사용하도록 하겠습니다.
어댑터 패턴을 현 상황에 맞게 간단하게 설명하자면, FrontController는 Adapter(규격)를 의존하고 Controller(V3, V4, V5)들은 Adapter 규격에 맞는 각각의 Adapter(V3, V4, V5)를 만들어서 FrontController에게 제공해주면 됩니다. 이후 FrontController는 다른 규격의 Controller들을 동시에 다룰 수 있습니다.
Adapter 인터페이스를 정의했습니다. Adapter 규격은 기존의 ControllerV3와 유사하게 ViewModel을 반환하도록 정의하겠습니다.
이제 각 Controller(V3, V4, V5)의 Adapter를 만들어보겠습니다.
Adapter는 V3를 따르고 있기 때문에 ControllerV3의 어댑터는 크게 달라지는 점 없이 해당 ControllerV3에서 반환하는 ViewModel을 그대로 반환해주면 됩니다.
ControllerV4의 어댑터는 해당 ControllerV4에 model을 Map으로 넘겨줘야 하고, View의 논리 이름만을 반환하기 때문에 Adapter 규격에 맞게 ViewModel을 직접 만들어서 반환해주도록 합니다. 이게 어댑터의 역할입니다.
ControllerV5의 어댑터는 해당 ControllerV5는 View의 논리 이름만을 반환하기 때문에 Adapter 규격에 맞게 ViewModel을 직접 만들어서 반환해주도록 합니다.
모든 Controller의 Adapter를 만들었습니다. 이제 FrontController에서 모든 버전의 Controller의 Adapter를 통해서 요청을 처리하도록 바꿔보겠습니다.
FrontController의 생성자 부분입니다. Controller를 저장하고 있는 Map을 이제는 여러 버전의 Controller를 저장해 야하기 때문에 Object로 바꾸었습니다. (Object가 맘에 안 들면 기존 여러 버전의 Controllable 인터페이스를 한 단계 더 추상화하는 방법도 있습니다.) 그리고 각 버전의 Adapter도 List로 저장했습니다.
이제 사용자 요청이 들어오면 FrontController는 요청을 수행할 수 있는 Controller를 찾고, 해당 Controller를 수행할 수 있는 Adapter를 찾아 기능을 수행하면 됩니다. 해당 기능을 구현해보도록 하겠습니다.
사용자 요청이 들어오면 요청을 수행할 수 있는 Controller를 찾습니다. 해당 Controller는 여러 버전의 Controller 중 하나이기 때문에 FrontController에서 수행할 수 있는 Adapter를 Adapter 목록에서 찾습니다. Adapter의 기본 규격은 ViewModel을 반환하기 때문에 반환한 ViewModel을 이용하여 View를 랜더링 합니다.
프리코스를 진행하면서 단지 제가 알고 있던 MVC 패턴과 상이한 부분에 대해서 찾아보다가 MVC에 대해서 딥하게 공부할 수 있었습니다. 그런데 공부했던 과정을 블로그에 최대한 쉽게 담으려고 했는데 생각보다 쉽지 않네요.
열심히 정리한 만큼 많은 사람들에게 도움이 되었으면 좋겠습니다!😊
[우아한 테크 코스 5기] 가장 강력한 예외 처리 방법은 무엇이 있을까요? (1) | 2022.12.11 |
---|---|
[우아한 테크 코스 5기] 프리코스 4주차 - 다리 건너기 회고 (0) | 2022.11.22 |
[우아한 테크 코스 5기] 프리코스 3주차 - 로또 회고 (0) | 2022.11.15 |
[우아한 테크 코스 5기] 프리코스 2주차 - 숫자 야구 게임 회고 (0) | 2022.11.08 |
[우아한 테크 코스 5기] 프리코스 1주차 - 온보딩 회고 (0) | 2022.11.01 |