[Java] MVC 패턴 자세하게 알아보자!

Dev_ch·2023년 9월 5일
6
post-thumbnail

그동안 Spring으로 서버를 개발할때 패키지 구조를 domain 형식으로 자주 가져갔었다. 그런데 정작 왜 이렇게 가져가야되나? 라는 질문에 제대로 대답을 하지 못할 것 같아 기본적인 Java의 MVC 패턴에 대해 알아보려한다. 특히 이번 우테크 프리코스를 준비하기 위해서 MVC에 대한 이해도가 높아야 된다고도 생각했고 객체지향적인 개발을 위해 기본을 이해하는 것은 필수요소라고 생각했다.


👀 MVC 패턴이란?

💡 MVC는 애플리케이션의 사용자 인터페이스, 비즈니스 로직, 데이터의 세 가지 주요 구성 요소를 분리하여 각 요소가 서로에게 미치는 영향을 최소화하기 위한 목적을 가지는 소프트웨어 디자인 패턴이다.

1970년대, Xerox PARC 연구소에서 초기 디자인을 통해 처음으로 세상에 나오게 되었는데, 초기에는 컴퓨터 응용 프로그램의 GUI를 위한 설계 패턴으로 시작되었다고한다.

우리가 MVC 패턴이라하면 주요 구성 요소가 각각 Model, View, Controller 임을 알 수 있다.

  1. Model : 애플리케이션의 데이터와 비즈니스 로직을 포함한다. 데이터베이스와 상호 작용, 데이터 처리 및 검증과 같은 작업을 처리하게 된다.
  2. View : 사용자에게 정보를 표시하는 역할을 한다. 사용자 인터페이스와 데이터의 표현을 담당하며 사용자의 요청에 따라 정보를 갱신한다.
  3. Controller : 사용자의 입력을 받아 처리하고, 적절한 응답을 생성하기 위해 모델과 뷰를 조정한다.

헷갈리는 이유?

필자의 경우 Model은 쉽게 이해할 수 있었다. 근데 Controller와 view가 정확히 어떻게 역할을 수행할까? 라는 의문이 조금 들었다. 그래서 이 부분을 정확하게 이해하기 위해 예시코드를 봐보자.

// Model
class GameModel {
    private int targetNumber;
    
    public GameModel() {
        this.targetNumber = (int) (Math.random() * 10) + 1;
    }

    public boolean isCorrect(int guess) {
        return guess == targetNumber;
    }
}

// View
class GameView {
    public int getUserInput() {
        Scanner scanner = new Scanner(System.in);
        System.out.print("1부터 10까지의 숫자 중 하나를 입력하세요: ");
        return scanner.nextInt();
    }

    public void displayResult(boolean isCorrect) {
        if (isCorrect) {
            System.out.println("정답입니다!");
        } else {
            System.out.println("틀렸습니다. 다시 시도하세요.");
        }
    }
}

// Controller
class GameController {
    private GameModel model;
    private GameView view;

    public GameController(GameModel model, GameView view) {
        this.model = model;
        this.view = view;
    }

    public void play() {
        while (true) {
            int userGuess = view.getUserInput();
            boolean result = model.isCorrect(userGuess);
            view.displayResult(result);
            
            if (result) break;
        }
    }
}

// Main function
public class NumberGuessingGame {
    public static void main(String[] args) {
        GameModel model = new GameModel();
        GameView view = new GameView();
        GameController controller = new GameController(model, view);

        controller.play();
    }
}

Controller는 model과 view를 참조하여 사용자의 입력에 따라 Model을 업데이트 하고, 적절한 View를 표시한다고 보자.

View는 애플리케이션 단위의 개발이 아니라면 웹페이지나 JSP 같은 기능에서 수행될 수 있지만, Java의 콘솔 기반 애플리케이션이라고 한다면 View는 결국 사용자와의 상호작용을 관리하기 위한 콘솔 입력/출력을 다루는 코드가 된다.

View를 이해하기 위해선 1차적으로 웹 페이지를 생각하면 된다.

이게 사용자가 보는 화면 View 라고 생각해보자. 여기서는 사용자가 입력을 할 수 있고 로그인을 했을 경우 출력을 볼 수 있다. 하지만 Java로만 기능을 구현하여 웹 애플리케이션의 컨텍스트를 고려하지 않는다고 했을때 우리가 입/출력을 받을 수 있는 공간은 콘솔이다.

결국 View는 콘솔의 입/출력을 관리하게 되는 책임을 갖게된다. 그리고 Controller는 Model과 View의 상호작용을 관리하게 된다.

public class NumberGuessingGame {
    public static void main(String[] args) {
        GameModel model = new GameModel();
        GameView view = new GameView();
        GameController controller = new GameController(model, view);

        controller.play();
    }
}

콘솔 기반의 애플리케이션을 main 함수를 통해 실행시킨다 했을때 Controller의 생성자를 통해 Model과 View를 주입하여 controller 단위에서 주요 로직이 수행하게 되는 것 이다.

Model, View, Controller에 대한 정의를 아래를 참고해 조금 더 자세하게 파악해보자.

Model

Model은, 객체의 청사진이다. 애플리케이션의 데이터를 저장하고, 비즈니스 로직을 수행한다. 보통은 데이터베이스를 통해 데이터를 가져오게 되지만 개발하는 목적에 따라서 객체, XML 파일 등 다양한 출처로부터 데이터를 가져오게 된다.

위 코드처럼 간단한 게임을 제작할때 객체를 통해 Model의 데이터를 가져오고 있다.

View

View는, 데이터의 표시다. 하나의 Model에 대해 다양한 View가 존재할 수 있다. 예를 들어 OO회사의 실적 Model이 존재한다고 했을때 View는 하나의 차트가 될수도 그래프, 표가 될 수 있다. 어떠한 데이터를 실질적으로 보여주는 곳 이라고 생각하자.

또한, Model의 데이터가 변경되면 View는 자동으로 갱신되어야 한다. OO회사의 실적 Model이 어제보다 더 상승했는데 View는 어제의 실적을 보여주면 안된다는 것 이다. 동적 갱신을 중요하게 여겨야 하며 Observer 패턴을 통해 구현될 수 있다.

Controller

Controller는, 중개자라고 생각하자. 사용자에게 받은 입력을 통해 Model과 View를 적절히 업데이트 해주어야 한다. 또한 입력, 요청을 통해 애플리케이션의 다른 부분에 명령을 내리거나 Model이 이해할 수 잇는 형식으로 변환해주는 역할을 한다.

🔍 MVC 패턴을 왜 사용해?

MVC 패턴의 작동원리는 보통 아래와 같다.

  1. 사용자는 Controller를 통해 Application과 상호 작용한다.
  2. Controller는 사용자의 요청을 해석하고, 필요한 데이터를 Model에 요청한다.
  3. Model은 요청된 데이터를 처리하고, 결과를 Controller에 반환한다.
  4. Controller는 반한된 데이터를 View에 전달하여 사용자에게 알맞게 표시한다.
  5. 사용자는 업데이트된 View를 보게 된다.

여기서 2번을 보게되면 Controller에서 사용자의 요청을 해석한다고 되어있다. 어 그러면, Controller에 매개변수를 넘겨주는건 어떨까? 라는 생각을 하게된다. View에서 입력을 받는것이 아닌 main() 함수에서 입력을 받고 Controller에게 넘겨준다고 해보자.

    public static void main(String[] args) {
        GameModel model = new GameModel();
        GameService service = new GameService(model);
        GameView view = new GameView();
        GameController controller = new GameController(service, view);

		// main에서 입력
		Scanner scanner = new Scanner(System.in);
        int number = scanner.nextInt();

        controller.play(number);
    }

일반적으로는 입력을 View에서 처리하는 것이 MVC 패턴의 원칙에 잘 부합하지만 어떠한 구조가 최선인지는 상황에 맞추어야한다. 이러한 점을 고려하고 개선된 설계를 해나아가는 것이 객체지향의 맛이 아닐까 싶다. 위와 같은 구조가 틀렸다고는 할 수 없다.

장점

MVC 패턴을 적용하면서 얻는 장점은 아래와 같다.

  1. 관심사의 분리 : MVC는 각 구성 요소를 별도로 유지하게 되면서 개발과 유지보수를 용이하게 만들어준다. MVC 패턴을 사용하는 핵심적인 요소라고 볼 수 있다.
  2. 유연성 : 각 구성 요소의 독립성으로 인해 애플리케이션의 일부분만 수정하거나 새로운 기능이나 요구사항을 확장하기 쉽다.
  3. 재사용성 : Model과 View는 재사용이 가능하기에 다양한 상황에서 같은 로직이나 인터페이스를 사용할 수 있다.

각각 수행해야되는 기능, 즉 관심사의 분리를 통해 여러 장점을 얻어가는 패턴이라고 생각하면 된다.

단점

그렇다고 무조건적인 장점이 있다고는 할 수 있을까? MVC 패턴은 관심사의 분리를 통해 각각의 클래스들과 패키지가 무슨 기능을 하는지 쉽게 파악할 수 있지만, 반대로 MVC 패턴에 관해 지식이 부족하면 여러 클래스들간의 협력으로 인해 파악하기 어려워 질 수 있다.

또한 간단한 애플리케이션 이라면 MVC 패턴을 적용하지 않는 것이 오히려 더 좋을 수 있다. 많은 클래스를 필요로 하지 않는데 MVC 패턴을 적용하기 위헤 Model,View,Controller 단위로 관심사를 분리하게 되면 오히려 코드를 파악하는데 도움을 주지 못하고 구축의 복잡성, 시간이 다소 오래걸릴 수 있다.

🚀 MVC 패턴은 어디서 활용돼?

MVC 패턴은 결국 웹 애플리케이션 및 소프트웨어 개발에서 주로 활용된다. 지금까지 해왔던 Spring을 이용한 개발도 결국 MVC 패턴을 따르는 것 이다. Spring에서 패키지 구조가 domain 형식 또는 layer 형식 이든 MVC를 기준으로 관심사를 분리하여 코드의 개발과 유지보수를 높이고 있었다.

여기서 중요한 점은 대규모 웹 애플리케이션 개발의 복잡성을 관리하기 위해서 MVC 패턴을 기반으로한 더욱 계층화된 아키텍쳐 라고 볼 수 있다.


📈 더욱 계층화된 애플리케이션

MVC는 관심사의 분리를 통해 개발과 유지보수성 등을 높이는 것임을 알 수 있었다. 그리고 우리는 대규모 웹 애플리케이션을 개발할때, Model, view, Controller 만으로는 관심사를 쉽게 분리할 수 없다.

// Model
class GameModel {
    private int targetNumber;
    
    public GameModel() {
        this.targetNumber = (int) (Math.random() * 10) + 1;
    }

    public int getTargetNumber() {
        return targetNumber;
    }
}

// Service
class GameService {
    private GameModel model;

    public GameService(GameModel model) {
        this.model = model;
    }

    public boolean isGuessCorrect(int guess) {
        return guess == model.getTargetNumber();
    }

    public String provideHint(int guess) {
        if (guess > model.getTargetNumber()) {
            return "더 낮은 숫자를 시도해보세요!";
        } else {
            return "더 높은 숫자를 시도해보세요!";
        }
    }
}

// View
class GameView {
    public int getUserInput() {
        Scanner scanner = new Scanner(System.in);
        System.out.print("1부터 10까지의 숫자 중 하나를 입력하세요: ");
        return scanner.nextInt();
    }

    public void displayResult(boolean isCorrect) {
        if (isCorrect) {
            System.out.println("정답입니다!");
        } else {
            System.out.println("틀렸습니다. 다시 시도하세요.");
        }
    }

    public void displayHint(String hint) {
        System.out.println(hint);
    }
}

// Controller
class GameController {
    private GameService service;
    private GameView view;

    public GameController(GameService service, GameView view) {
        this.service = service;
        this.view = view;
    }

    public void play() {
        while (true) {
            int userGuess = view.getUserInput();
            if (service.isGuessCorrect(userGuess)) {
                view.displayResult(true);
                break;
            } else {
                view.displayResult(false);
                view.displayHint(service.provideHint(userGuess));
            }
        }
    }
}

// Main
public class NumberGuessingGame {
    public static void main(String[] args) {
        GameModel model = new GameModel();
        GameService service = new GameService(model);
        GameView view = new GameView();
        GameController controller = new GameController(service, view);

        controller.play();
    }
}

Model 의 책임

애플리케이션을 개발하게되면 Model은 데이터의 정의와 데이터베이스와의 직접적인 상호작용에 중점을 두게 된다. 근데 만약 Model 객체안에서 주요 비즈니스 로직을 구성해서 사용하다보면 데이터를 정의하고 데이터베이스와의 상호작용에 대한 책임 뿐만 아니라 직접적인 비즈니스 로직을 수행하는 책임 또한 갖게된다.

그래서 Model은 계층화를 통해 관심사를 분리해줄 수 있다.

Service 의 책임

Model이 갖고있던 직접적인 비즈니스 로직을 수행하는 책임은 Service 계층으로 분리시켜 관심사를 분리할 수 있다. Model은 오로지 데이터 정의, 데이터베이스 상호작용만 하게하고 실제 로직이 수행되는 부분은 Service에서 관리하는 것 이다.

더 많은 관심사의 분리를 통해 각각의 책임을 더욱 명확하게 할 수 있고 코드의 재사용성을 더욱 높일 수 있다. 그리고 가장 중요한 것은 테스트를 수행하기 쉬워진다는 것 이다. 각 계층마다의 단위 테스트, 통합 테스트를 작성하기 쉬운 구조로 만들 수 있다.

그 외 Repository, DTO, Config 등등

결국 관심사를 분리하는 것은 객체지향개발에서 매우 중요하고 MVC 패턴을 그대로 가져가는 것이 아니라 애플리케이션을 개발할때 요구사항에 맞추어 MVC 패턴을 기반으로 계층화된 구조를 얼마나 잘 가져가냐에 코드의 질적 향상을 가져온다.

우리는 보통 하나의 domain에 Repository, DTO, Config, exception 등 관심사를 분리하기 위한 패키지, 클래스 등을 구성하게 된다.

결국 핵심은 MVC 패턴도 결국 객체지향 개발의 유지보수, 재사용, 확장성 등을 높이기 위해 사용되는 것 이다.

🫨 MVC 패턴은 무조건적으로 적용해야할까?

프로젝트의 규모와 복잡성

MVC 패턴도 결국 객체지향적인 개발을 더욱 효율적으로 하기위해 적용하는 패턴일 뿐이다. 무조건적이 아닌 프로젝트의 요구 사항, 규모를 고려해서 적용하는 것이 좋다고 판단되면 적용을 하는 것 이다. MVC 패턴은 많은 개발자들에게 익숙하고 보편적인 패턴이지만 그 원칙과 각 구성 요소의 역할을 정확하게 이해하는 것이 중요하다.

MVC 패턴의 구성 요소의 분리가 과도하게 진행될 수록 전체 애플리케이션의 구조의 복잡성은 더욱 올라가게 된다. 간단한 웹 페이지나 작은 애플리케이션이면 MVC를 적용하는 것은 과도한 설계가 될 수 있다.

MVC는 함께 이해해야 한다

MVC는 관심사를 분리하는 것이 중점이다보니 앞서 말했듯 프로젝트의 복잡성이 올라갈 수 있다. 이렇게 되면 MVC 패턴에 대해 이해도가 낮거나, 또는 이해를 하기위한 시간이 오래걸릴 뿐더러 비즈니스 로직에 집중하는 것이 아닌 패턴 자체에 시간을 할애해 오히려 실제 기능을 소홀히 할 수 있게 된다.

알맞은 프레임워크를 사용하는게 좋아

Spring, Django는 MVC 아키텍쳐를 기반으로 구성되어잇기에 MVC 패턴을 따르는 것이 자연스럽게 진행되고 더욱 편하게 개발할 수 있다. 하지만 모든 프레임워크 또는 라이브러리가 MVC를 기반으로 하는 것은 아니다. 예를 들어, React.js는 View에 중점을 두고있다.

즉 도구를 사용할때는 MVC를 강제로 하는 것이 아닌 해당 도구의 패턴을 따르는 것이 좋다. 일상생활에서 우리가 도구를 지정된 사용방법 또는 사용하는 곳에 맞추어 사용하듯이 프레임워크와 라이브러리 같은 것들도 같다고 생각하자. 도마를 사용할때 A4를 올려 평평하게 맞추어 글을 작성할 수는 있지만, 실제 용도나 사용법은 아니다. 이렇게 생각하자!

변경 및 확장이 많을까?

MVC는 확장성에 있어 강점을 보인다. 근데 만약, 한 번만 실행되거나 변경이 되지 않는, 확장이 되지 않는 프로젝트에서 MVC 패턴이 갖고있는 장점을 가져갈 수 없다. 아무래도 소규모의 프로젝트, 기능일 수록 MVC 패턴을 적용하는 것에 대해선 고민을 해보아야한다.😆


💭 결론

MVC 패턴은 많은 장점을 제공하지만, 항상 모든 상황에서 최선의 선택이 될 수 있다고는 말할 수 없다. 결국 이러한 부분은 프로젝트 또는 기능의 요구사항, 팀-팀원간의 경험, 사용중인 도구 및 기술, 프로젝트의 방향성에 대한 고려가 필요하고 적절한 아키텍처 패턴을 적용하는 것이 좋다

MVC 패턴이 대략적으로 무엇인지만 알고있었지만 조금 더 자세하게 공부해보고 예시코드를 통해 이해하다보니 더욱 쉽게 이해할 수 있었다. 간단한 MVC는 Model이 실제 로직을 수행한다는 점을 공부하게 되었고 이를 더욱 계층화에 Service가 해당 책임을 가져가는 것임을 알게 되었다.

Spring은 MVC 아키텍쳐 기반의 프레임워크이다보니 MVC에 대한 이해도가 높아야 했는데, 이제서야 더욱 잘 알아가는 느낌이다. 이렇게 공부하면서 우테크 기능 설계도 조금 더 자신감 있게 할 수 있을 것 같다. 아무튼 이번 포스팅은 이렇게 마무리를 해보도록 하자 👍

profile
내가 몰입하는 과정을 담은 곳

0개의 댓글