MVC

MVC 패턴 맛만보기

간략 정의

MVC 패턴은 매우 흔하게 볼 수 있는 패턴이다. 윈도우 폼, 웹, 앱 등에서 볼 수 있다. 유명하고 빈번하게 사용된다는 것은 간단하면서 매우 중요하다는 것을 의미한다고 볼 수 있다.

MVC 는 Model View Controller 의 앞 글자를 따서 만든 이름이다.

Controller, Model, View 를 각각 분리하자는 것이다. 장점은 model 과 view 가 각각의 책임이 명확해지고 모듈처럼 재사용할 수 있게 된다.(물론 코드를 잘 짜야한다) 단점은 model 크기가 너무 커져 관리하기가 어렵게 된다는 점이다.

Model

model 은 특정 로직을 한 묶음으로 묶은 것이라고 볼 수 있다. 묶음이 큰 묶음일 수 있고 작은 묶음일 수 있다.

예부터 보면 대부분의 서비스에서는 회원 기능이 있다. DB 에 쿼리를 통해 값을 처리하는 AuthModel 을 만든다고 하면 아래와 같은 기능들이 필요할 것이다.

AuthModel

  • 특정 ID 를 가진 회원 조회하기
  • 마지막 로그인 일자 기록하기
  • 휴먼 계정 조회하기
  • 전화번호를 기준으로 조회하기

AuthModel 은 회원과 관련된 기능을 담고 있다. model 이라고 꼭 DB 를 사용해야하는 것은 아니다. 예를 들어 JWT 토큰을 발행하는 로직이 들어갈 수도 있는 것이다.

AuthModel 을 만들었을 때의 장점은 회원 관련된 기능을 찾거나 사용할 때 AuthModel 안에서 찾아서 사용하면 된다. 그리고 AuthModel 안에서만 사용할 로직이 있다면 외부에서 접근을 못하게 숨기고 사용할 수 있다는 것이다.

즉 다시 정리하자면 model 은 로직의 묶음이라고 볼 수 있다.

밑에서 다시 언급하겠지만 model 은 controller 에서만 사용할 수 있다.

View

view 는 말 그래도 화면에 보여지는 것이다. view 가 분리되지 않는 예는 아래로 들 수 있다. view 를 분리하지 않아 아래 예제처럼 작성되었다면 로그인 화면 디자인이 변경되었을 때 server 코드가 변경이 되어야한다.

view 가 독립적으로 분리되어있다면 html 파일만 변경하면 되는 것이다.

뷰가 분리된 상황

return 'main.html'

뷰가 분리 안된 상황

if 로그인이 안되어있을 때
    return '<input name='ID' /> <input name='PW' />
else 로그인이 되었을 때
    return '<table>...

Controller

controller 는 model 에서 디테일한 로직을 처리하고 view 에 값을 전해주는 것이다.

controller 는 큰 범위를 controller 하는 것이다.

예를 들어 controller 에서 로그인 관련된 로직을 나열하면 어떠한 로직을 처리하는지 가독성이 떨어지게 된다. 그렇지만 controller 에서 model 에 있는 로그인 함수를 사용하여 로그인이 성공하면 user 객체를 반환하고 로그인이 실패했으면 null 을 반환한다면 controller 에서는 아래와 같이 처리하면 된다.

controller 와 model 을 분리하지 않았을 때

// AuthController
if 디비 연결 == null
    예외 발생
else
    if db.exec(ID 로 조회) == null
        return 'login.html'
    if db.exec(ID & PW 로 조회) == null
        비밀번호 실패 횟수 +1
        return 'login.html'
    else
        return 'main.html'

controller 와 model 을 분리했을 때

// AuthController
if authModel.login(USER_ID, USER_PW) == null
    return 'login.html'
else
    return 'main.html'

Alt text

예제 코드

예제로는 DB 에는 사용자 정보가 있는데 만 나이로 법률이 개정이 되면서 2살 줄이는 것이다. 조건은 한국사람만 해당이 된다.

만약 controller 가 model 과 view 를 분리되지 않는다면 아래와 같은 코드 일 것이다.

const database = {
    user: [
        {
            name: 'foo',
            age: '20',
            country: 'kr',
            lastModified: '2022-06-11T11:40:38.736Z',
        },
        {
            name: 'bar',
            age: '30',
            country: 'kr',
            lastModified: '2022-06-10T11:40:38.736Z',
        },
        {
            name: 'zoo',
            age: '40',
            country: 'uk',
            lastModified: '2022-06-19T11:40:38.736Z',
        },
    ]
};

const Controller = () => {
    const users = database.user;
    const usersByCountryKr = [];
    for (const user of users) {
        if (user.country === 'kr') {
            usersByCountryKr.push(user);
        }
    }

    for (const userKr of usersByCountryKr) {
        userKr.age -= 2;
        userKr.lastModified = new Date().toISOString();
    }

    for (const user of database.user) {
        console.log(`이름 : ${user.name}`)
        console.log(`나이 : ${user.age}`)
        console.log(`국가 : ${user.country}`)
        console.log(`최종 수정 일자 : ${user.lastModified}`)
        console.log(`-------------`)
    }
}

위 코드는 아래와 같이 리펙토링?!이 가능하다. 패턴 이해를 돕기위해 코드를 펼쳤으니 이해 해주시기를 바란다.

const Controller = () => {
    database.user
        .filter(({country}) => country === 'kr')
        .map(user => {
            user.age -= 2;
            user.lastModified = new Date().toISOString();
        });

    for (const user of database.user) {
        console.log(`이름 : ${user.name}`)
        console.log(`나이 : ${user.age}`)
        console.log(`국가 : ${user.country}`)
        console.log(`최종 수정 일자 : ${user.lastModified}`)
        console.log(`-------------`)
    }
}

MVC 패턴이 적용된 코드

const database = {
    user: [
        {
            name: 'foo',
            age: '20',
            country: 'kr',
            lastModified: '2022-06-11T11:40:38.736Z',
        },
        {
            name: 'bar',
            age: '30',
            country: 'kr',
            lastModified: '2022-06-10T11:40:38.736Z',
        },
        {
            name: 'zoo',
            age: '40',
            country: 'uk',
            lastModified: '2022-06-19T11:40:38.736Z',
        },
    ]
};

const Model = {
    getUserList: () => {
        return database.user;
    },
    selectByCountry: (countryId) => {
        return database.user
            .filter(({country}) => country === countryId);
    },
    incrementAge: (users, amount) => {
        for (const user of users) {
            user.age += amount;
            user.lastModified = new Date().toISOString();
        }
    }
}

const View = {
    userList: (users) => {
        for (const user of users) {
            console.log(`이름 : ${user.name}`)
            console.log(`나이 : ${user.age}`)
            console.log(`국가 : ${user.country}`)
            console.log(`최종 수정 일자 : ${user.lastModified}`)
            console.log(`-------------`)
        }
    }
}

const Controller = () => {
    const usersByCountryKr = Model.selectByCountry('kr')
    Model.incrementAge(usersByCountryKr, -2);
    View.userList(Model.getUserList());
}


Controller();

controller 부분은 아래와 같이 사용할 수도 있다.

const Controller = () => {
    Model.incrementAge(Model.selectByCountry('kr'), -2);
    View.userList(Model.getUserList());
}

긴 설명보다는 좋은 코드가 훨씬 도움이 되는데 나름 예제를 고심해서 작성했지만 이해의 도움이 되었을지는 모르겠습니다. 조금이나마 도움이 되셨기를 바래봅니다!


리펙토링
Strategy
NCloud LB & SourcePipeline 구축하기
tech collection 서비스 성능 개선하기
Selenium 복권 구매 자동화 만들어보기
디자인 패턴
책 리뷰
블로그 챌린지