디자인 패턴

각 디자인 패턴은 기존 환경 내에서 반복적으로 일어나는 문제들을 설명한 후, 그 문제들에 대한 해법의 핵심을 설명해 줍니다. 똑같은 방법으로 두 번 하지 않고 이 해법을 100만 번 이상 재사용할 수 있도록 말이죠 - 크리스토퍼 알렉산더

어떤 상황의 문제에 대한 (재사용 가능한) 해법

특정한 전후 관계에서 일반적 설계 문제를 해결하기 위해 상호 교류하는 수정 가능한 객체와 클래스들에 대한 설명

for OOD

패턴의 요소

패턴 이름(pattern name)

  • 고도의 추상화
  • 의사소통 간결

문제(problem)

해법(solution)

  • 설계를 구성하는 요소들
  • 요소들 간의 관계, 책임, 협력

결과(consequence)

  • 패턴 적용 후 얻는 결과와 장단점

관계

relationship

점선관계는 상대적으로 실선관계보다 결합도가 낮다. 즉 점선 관계가 상대적으로 유연성이 높다

dependency(의존)

  • 메소드 인자 타입
  • 메소드 반환 타입
  • 메소드 내 지역 변수 타입
  • 큰 의미로는 연관관계도 의존관계의 하위 집합으로 분류할 수 있다고 본다. (개인생각)

Instance-Level

association(연관)

  • 객체 레퍼런스를 객체 변수(field)로 지니고 있는가?

aggregation(집합) - 표준스팩 정의가 모호하므로 사용 비추천

  • 부분과 전체 : 표준스팩이 이렇게만 설명하고 끝이다 ;;;;
  • owner ship (이 부분은 표준스팩이 모호한 면이 있어서 참고로만 알아두자)
    • 어떤 전체의 부분이 되는 객체가 다른 전체의 부분으로 되면 안된다.
    • 부분의 ownership를 가지는 객체는 단 하나여야한다.

composition(합성)

  • lifecycle이 같은가?

Instance-Level 관계

association

Type-Level

generalization(일반화)

클래스 상속

상속은 일반화로 부를 수 있지만 일반화 중 상속으로 부를 수 없는 경우도 있다.

상속과 일반화를 같은 의미로 쓰는 것이 틀렸다고 보기는 힘들다고 본다. 단, 두 개념이 완벽하게 같다고 보는 것은 경계하자.

realization(실체화)

인테페이스 구현

용어정리

class

  • 속성과 행위를 가지는 추상화된 것

객체(object)

인스턴스(instance)

객체? 인스턴스?

인스턴스 : 분류를 기반으로 한 추상화 된 것에서 구체화된 것

  • 인스턴스화(instantiation) : 클래스에서 객체로
  • 분류(classification) : 객체에서 클래스로

인스턴스는 객체보다 좀 더 추상화된 개념이다

  • 객체는 클래스의 인스턴스다.
  • Row는 DB테이블의 인스턴스다.
  • 객체 간의 링크는 클래스 간의 연관관계의 인스턴스다.
  • 실행 프로세스는 프로그램의 인스턴스다.

속성(attribute)

  • field

property? attribute?

  • property : 외재속성(getter)
  • attribute : 내재속성(field)
  • 캡슐화

행위(behavior)

  • 메서드

메세지(요청)

객체 간 협력 가능하게 하는 방법

  • 객체는 메시지를 사용자(client)에게 받으면 연산(행위)을 수행
  • 요청은 객체가 연산(행위)를 실행하게 하는 유일한 벙법
  • 속성은 행위를 통해서만 변경 가능

시그너처(signature)

  • 연산의 이름, 매개변수들, 반환타입의 명세
  • java 등의 언어에서는 반환타입이 메소드 시그너처로 포함되지 않는다.

인터페이스

  • 객체가 정의하는 연상의 모든 시그너처
  • 객체가 받아서 처리할 수 있는 연산의 집합
  • 외부에 오픈

타입

  • 특정 인터페이스의 명칭

개념 -> 타입 -> 인터페이스(or 클래스)

다형성(polymorphism)

OOP에서는 사용자가 기대하는 객체를 동일한 인터페이스를 갖는 다른 객체로 대처할 수 있게 합니다. 이 대체성을 다형성이라고 합니다.

동일한 인터페이스를 갖는 것으로 추상화 시킬 수 있고 이로 인해 단순화할 수 있으며, 객체 간의 결합도를 낮출 수 있습니다. 인터페이에 대해서만 의존성을 가지고 있으면 이에 따른 어떤 구체화된 것을 사용하는지에 대해서 몰라도 되기 때문입니다.

디자인 패턴은 인터페이스에 정의해야 하는 중요 요소가 무엇이고 어떤 종류의 데이터를 주고 받아야 하는지 식별하여 인터페이스를 정의하도록 도와줍니다.

OO에서 확장에 열리는 부분(OCP)을 이 다형성이 가능하게 합니다.

구상클래스(Concrete Class)

  • 구체화된 클래스
  • 구현된 상세클래스
  • 단단하다(concrete) -> 유연하지 못함

클래스 상속 vs 인터페이스 상속

구현이 아닌 인터페이스(추상화)에 따라 프로그래밍합니다.

  • 추상화에 의존하면 의존성이 낮아지므로 더 유연한 프로그래밍이 가능하다.
  • 인터페이스라는 객체군을 형성해서 자연스레 다형성을 끌어낼 수 있다.

상속 vs 구성

상속 : is a, is a kind of 구성(합성) : has a

  • 상속의 경우 부모클래스의 내부가 서브클래스에 공개되기 때문에 일부 캡슐화가 위배된다
  • 구성은 상속의 대안으로써 다른 객체의 참조를 얻어서 새로운 기능 혹은 객체를 구성하는 것
  • 사용하는 객체의 내부가 공개되지 않는다 (참조를 가진 객체의 인터페이스를 통해서만 접근 가능)
  • 상속은 컴파일 시 결정, 구성은 런타임 시 결정

객체 합성이 클래스 합성보다 더 나은 방법

자바 컨퍼런스에서 자바를 창시한 제임스 고슬링에게 한 프로그래머가 물었답니다. “자바를 다시 설계한다면 무엇을 없애고 싶습니까?” 그러자 고슬링이 “클래스를 없애고 싶습니다” 라고 했다더군요. 물론 다시 웃으면서 “그만큼 클래스 상속이 문제가 많으니 인터페이스 상속을사용해야 한다는 뜻이다” 라고 덧붙이긴 했답니다.

예시

  • 상속(is a) : 템플릿 메서드 패턴
  • 구성(has a) : 전략 패턴

위임

  • 사전적인 의미는 맡긴다
  • 구성된 다른 객체(has a)에 처리를 책임을 넘기는 것
  • 처리를 메세지를 통해서 위임한다

디자인 패턴을 고르는 방법

  1. 패턴이 어떻게 문제를 해결하는가?
  2. 패턴의 의도를 파악
  3. 패턴들 간 관련성을 파악
  4. 비슷한 목적의 패턴을 그룹화
  5. 재설계의 원인 파악
  6. 설계에서 가변성을 가져야하는 부분이 무엇인지 파악

참고

  • GoF의 디자인패턴 - GoF
  • 자바 객체지향 디자인 패턴 - 정인상 외
  • UML 실전에서는 이것만 쓴다 - 로버트.C.마틴
  • 객체지향의 사실과 오해 - 조영호

정의

프로세스들이 더 이상 진행하지 못하고 영구적으로 block된 상태

예를들면

  1. 꽉 막힌 4거리 교차로
  2. 서로 멱살을 잡으면서 놓으라고 함

데드락 발생 4조건

  1. 상호배제(Mutual exclusion)
  2. 잠금과 대기(Hold & Wait)
  3. 선점 불가(No preemption)
  4. 순환 대기(Circular wait)

위 4가지 조건을 모두 만족해야 데드락이 발생한다. 즉 4가지 중 하나라도 만족하지 못하면 데드락은 발생하지 않는다.

데드락 발생조건 상세

각 조건별로 상세 내역을 알아보자.

상호 배제(Mutual exclusion)

한 번에 오직 한 개의 프로세스만이 자원에 접근 가능

예를들면 데이터베이스 연결 풀, 쓰기용 파일 열기, 레코드 락, 세마포어

만약 프로세스의 수보다 자원의 수가 더 많으면 실질적으로 상호 배제가 깨진다고 볼 수 있다. 자원을 접근함에 있어서 제약이 없기 때문이다.

하지만 대부분의 경우 접근할 자원이 부족한 경우가 많다.

잠금과 대기(Hold & Wait)

한 개 이상의 자원을 가진(Hold) 프로세스가 다른 프로세스 소유의 자원을 기다리는 것(Wait)

선점 불가(No preemption)

한 프로세스가 다른 프로세스로 부터 자원을 빼앗지 못하는 것

순환 대기(Circular wait)

프로세스 연쇄적(환형)으로 자원을 대기하는 상태

예를 들면

P1, P2 : 프로세스

R1, R2 : 자원

P1R1을 점유하고 있고 P2R2을 점유하고 있는 상태에서 P1R2가 필요하고, P2R1이 필요한 상태

순환 대기

출처 http://beginnersbook.com/2015/04/deadlock-in-dbms/

이것이 환형이기 때문에 프로세서와 리소스의 수는 각각 N개 이상일 수 있다.

데드락을 깨자

데드락 무시

이 방법은 해당 어플리케이션이 데드락이 발생하지 않는다고 가정하 하는 것이다. 즉, 실제로는 데드락은 무시하면 안된다. 데드락 발생을 정상적인 동작이 아니라고 판단하는 것이고 최후의 수단으로 두는 방식

Ostrich algorithm : 데드락이 발생하면 시스템 재시작

데드락 회피

순환 대기가 발생하지 않도록 자원의 할당 상태를 검사한다.

은행원 알고리즘(Dijkstra)

프로세스가 자원을 요구할 때 시스템은 자원을 할당한 후에도 안정 상태로 남아있게 되는 지를 사전에 검사하여 교착상태의 발생을 회피하는 기법

  • 대출 : 자원할당
  • 은행의 잔고 : 자원
  • 대출상환 : 자원해제
  • 안전상태(safe) : 대출 후 잔고가 있음
  • 불안전상태(unsafe) : 대출 후 잔고가 마이너스가 됨

비교

은행원 알고리즘 기준

  1. 고객이 은행에 대출을 신청한다.
  2. 은행원은 대출이 가능한지 심사한다.
    1. 대출이 가능하면 대출해준다.(safe)
    2. 대출이 불가능하면 대출해주지 않는다.(unsafe)

어플리케이션 기준

  1. 프로세스가 자신에게 필요한 자원들의 선점을 요청
  2. 알고리즘이 자원들 선점이 가능한지 검증
    1. 자원들 선점이 가능하면(safe) 자원을 할당한다.
    2. 자원들 선점이 불가능하면(unsafe) 하면 요청을 거절한다. - 데드락의 위험이 있으므로

잠금과 대기 조건 깨기 와 일부 비슷해 보인다.

데드락 감지

프로세스를 모니터링해서 데드락을 유발하는 프로세스를 감지한다. 그 후

  1. 해당 프로세스를 죽인다.
  2. 데드락 상태였던 프로세스가 죽고난 자원을 풀어서 다른 프로세스에서 선점할 수 있게 열어둔다.

단점

CPU 사용율이 높아서 성능 하락

데드락 예방

상호 배제 조건 깨기

상호 배제 조건 자체를 비켜가기

  • 자원을 동시 사용가능하게 만듬 : ex) AtomicInteger
  • 프로세스 수보다 자원의 수를 더 늘림 : 상호 배제할 필요가 없어짐

하지만 대부분의 경우 자원은 제한적이다. 상호 배제 조건을 깨기는 매우 힘든 경우가 많음

잠금과 대기 조건 깨기

대기가 발생하지 않도록 해서 비켜가기

각 자원을 점유하기 전에 필요한 자원들이 다 확보 가능한지 확인한다. 만약 단 하나의 자원이라도 점유하지 못한다면 지금까지 점유한 자원을 몽땅 내놓고 처음부터 다시 시작한다.

단점

  • 기아(Starvation) : 한 프로세스가 계속해서 필요한 자원을 점유하지 못함
  • 라이브락(Livelock) : 여러 프로세스가 한꺼번에 잠금 단계로 진입하는 바람에 계속해서 자원을 점유했다 풀었다를 반복한다.
    • 예를 들면 길을 가다 사람 2명이 서로 마주쳤는데 서로 똑같이 왼쪽 오른쪽 피하면서 갈 길을 못가는 상태
    • Deadlock는 프로세스가 대기상태인데, Livelock는 프로세스가 계속 활성화된 상태에서 잠금상태에 빠짐.
    • Deadlock를 회피하기 위해서 프로그램을 잘못 만들게 되면 Livelock에 빠지는 경우가 많다.
  • 프로세스가 모든 자원을 할당받기 위해 오랜시간 대기상태에 있게 됨
  • CPU 사용율이 높아짐

선점 불가 조건 깨기

다른 프로세스로 부터 자원을 뺏어올 수 있게 한다. 하지만 모든 요청 관리의 난이도가 높음

순환 대기 조건 깨기

모든 프로세스가 일정 우선순위 를 가지고 그 순서로 자원을 할당하면 데드락을 예방할 수 있음

단점

  • 자원을 할당하는 순서와 자원을 사용하는 순서가 다를 수 있다. 그래서 대기시간이 길어질 수 있음
  • 때로는 순서에 따라 자원 할당하기가 어렵다.

참고

낙관적 오프라인 잠금

충돌이 감지되면 트랜젝션을 롤백해 동시 비즈니스 간 충돌을 방지한다.

낙관전 오프라인 잠금

낙관적 오프라인 잠금은 한 세션에서 커밋하려는 변경 내용이 다른 세션의 변경 내용과 충돌하는지 않는지 확인하는 방법으로 이 문제를 해결한다.

작동원리

1. 레코드 버전번호

시스템의 각 레코드에 버전번호를 연결

현재 세션 상의 데이타의 버전 번호와 저장된 레코드의 버전번호를 비교해서 유효하면 커밋을 성공적으로 수행하고 실패하면(즉, 다른 세션에서 이미 업데이트를 해서 일관성이 깨진 상태이면) 롤백을 수행한다.

2. UPDATE문의 where에 모든 필드 포함

비추천

3. 최초 조회결과 저장 후 커밋 직전 동일 쿼리 결과 비교

특정레코드가 읽기가 아닌 동적쿼리 결과에 의존하는 경우 사용

굵은입자잠금

객체 그룹을 하나의 잠금 항목으로 취급함으로써 특정한 일관성 없는 읽기 문제를 해결

예외

콜렉션 항목을 수정하는 경우라면 낙관적 오프라인 잠금으로 충돌을 감지할 수 없음

굵은입자 잠금으로 어느정도 가능하지는 않을까? 그럼 조회한 콜렉션의 버전을 관리해야하는데 녹록할까?

예시

소스코드관리시스템(SCM:source code management)

그러면?

비즈니스 객체를 병합(merge)하는 경우를 생각하면 낙관적 오프라인 잠금에 큰 가치를 더할 수 있다.

항상 마지막에 충돌체크를 하는것이 아니라 중간에 미리미리 커밋을 체크해서 복잡성을 줄일 수 있다면 그것도 의미있다.

사용시점

두 비즈니스 트랜잭션 간에 충돌 가능성이 낮은 경우 유용하다. 충돌 가능성이 높다면 비관적 오프라인 잠금을 사용하는 것이 바람직하다.

비관적 오프라인 잠금

한 시점에 한 트랜젝션만 데이터에 접근할 수 있게 해서 동시 비즈니스 트랜잭션 간 충돌을 방지한다.

비관적 오프라인 잠금

낙관적 오프라인 잠금의 가장 큰 문제는 충돌이 비즈니스 트랜잭션의 마지막에 감지된다는 것이다.

비관적 오프라인 잠금은 이러한 충돌을 미연에 방지한다. 즉, 작업을 시작할 때 대상 데이터에 대한 잠금을 획득함으로써 일단 비즈니스 트랜젝션을 시작하면 동시성 제어 문제 때문에 작업이 실패하는 경우가 거의 없다.

작동원리

잠금유형

  1. 배타적 쓰기 잠금(exclusive write lock) : 데이터를 편집하려는 경우. 두 비즈니스 트랜잭션이 동일한 레코드를 동시에 변경하지 못하게 함
  2. 배타적 읽기 잠금(exclusive read lock) : 항상 최신 데이터를 읽어야 하는 경우. 심각하게 동시성을 제한하므로 특별한 경우에만 사용
  3. 읽기/쓰기 잠금(read/write lock) : 위 두가지 방식의 장점을 결합

올바른 잠금유형 선택 시 고려사항

  • 시스템의 동시성 극대화(성능)
  • 비즈니스 요건 충족
  • 코드의 복잡성 최소화
  • 도메인 모델러와 분석가가 잠금 전략을 이해

잠금 관리자

대부분의 기업 어플리케이션 환경은 웹서버가 복수이므로 DB기반 잠금 관리자가 적합하다. Redis를 기반으로 전용 잠금자를 사용하는 것도 좋을 거 같다

교착상태(deadlock) 해결방안

  1. 잠금을 얻는데 timeout을 건다.
  2. 잠금을 얻을 수 없으면 바로 예외를 발생시킨다.

사용시점

동시 세션 간 충돌 가능성이 높다면 비관적 오프라인 잠금을 사용하는 것이 좋다.

비관적 오프라인 잠금은 낙관적 오프라인 잠금을 보완하는 방법이라는 것과 반드시 필요할 때만 비관적 오프라인 잠금을 사용해야 한다.

굵은 입자 잠금

하나의 잠금으로 여러 관련 객체의 집합을 잠근다.

굵은 입자 잠금(Coarse-Grained Lock)은 여러 객체를 다룰 수 있는 단일 잠금이다. 잠금 동작 자체를 간소화할 수 있으며, 그룹을 잠그기 위해 모든 멤버를 로드할 필요도 없어진다.

작동원리

단일 경합 지점 생성

객체 그룹을 잠그기 위한 단일 경합 지점을 만드는 것. 이를 통해 단 하나의 잠금으로 전체 집합을 잠글 수 있다.

  • 낙관적 오프라인 잠금 : 각 항목이 버전을 공유하면 단일 경합 지점을 만들 수 있다. (버전 공유)
  • 비관적 오프라인 잠금 : 그룹의 각 맴버가 잠금 가능 토큰(lockable token)을 공유해야하며 이를 잠금 (공유된 버전 잠금)
  • 루트 잠금 : 집합체(aggregate)의 루트를 잠금

버전 공유 버전 공유

공유된 버전 잠금 공유된 버전 잠금

루트 잠금 루트 잠금

잠금의 구현은 아주 다양하며 미묘한 차이는 더욱 다양하다. 각자의 요건에 맞는 구현을 찾아야 한다.

사용시점

굵은 입자 잠금을 사용하는 가장 중요한 이유는 비즈니스 요건을 충족하기 위해서이다.

굵은 입자 잠금을 사용할 때의 가장 긍정적인 효과는 잠금을 획득하고 해제하는 부담이 아주 적다는 것이다. 실질적인 잠금의 범위가 좁다.

굵은 입자 잠금을 원할하게 운영하려면 비정상적인 객체 관계를 만들지 않도록 주의해야 한다.

암시적 잠금

프레임워크나 계층 상위 형식 코드에서 오프라인 잠금을 얻을 수 있게 한다.

암시적 잠금

잠금 작업을 개발자가 일일이 할 것이 아니라 애플리케이션(정확히 말하면 프레임워크 등)이 암시적으로 처리하는 것이다.

예시 : 잠금 매퍼

단순하게 프록시로 암시적 잠금을 구현했을 시 흐름도

잠금 매퍼

원격 파사드

가는 입자 객체에 대한 굵은 입자 파사드를 제공해 네트워크 상 효율을 향상시킨다.

원격 파사트 예시

객체 지향 모델은 가능하면 작은 객체(Thin interface)일 때 유연성이 높아진다. 하지만 프로세스 간 또는 네트워크 간 호출일 시에는 작은 객체 일 경우 호출 비용이 매우 커진다.

그래서 원격 객체를 사용할 경우에는 굵은 입자 파사드[GoF]가 필요하게 된다

원격 파사드가 하는 일은 굵은 입자 메서드를 가는 입자 객체로 변환하는 것이다.

작동원리

파사드를 한 번 호출 하면 파사드가 도메인 객체를 여러번 호출할 수 있다.

간단한 경우 위 예시 그림과 같이 일반적인 주소 객체의 접근 및 설정 메서드 전체를 대량 접근자(bulk accessor)라고 하는 접근자 메서드와 설정자 메서드 하나로 대처한다.

복잡한 경우 여러가는 입자 객체의 원격 게이트웨이 역할을 할 수 있다. 예를 들면 주문 파사트 하나로 한 주문과 해당 주문의 모든 주문 품목, 그리고 경우에 따라서 약간의 고객 데이터까지 얻고 업데이트할 수 있다.

세분성(granularity)은 원격 파사드와 관련된 가장 까다로운 문제 중 하나다.

어느정도로 세분화 시킬것인가? 이슈인데,

  1. 유스케이스당 하나씩 작게 여러개의 파사드를 만드는 방법
  2. 상대적으로 일반화 시켜서 굵은 입자로 적은 수의 파사드를 만드는 방법 - 이 방법을 더 추천

파사드는 내부 시스템이 아닌 외부 사용자의 편의를 위해 설계된다. 따라서 클라이언트 프로스세스가 이러한 명령이 다른 명령이라고 판단한다면 내부적으로 동일한 명령이라도 다른 명령으로 취급된다.

다른 역할

  1. 보안(ACL) 적용
  2. 트랜젝션 제어 적용

원격 파사드를 사용할 때 가장 큰 실수는 도메인 논리를 삽입하는 것이다.

원격 파사드는 도메인 논리가 아니다

파사드는 최소한의 역할만 포함하는 얇은(thin) 포장이어야 한다. 단지 도메인 논리를 호출할 뿐이다.

데이터 전송 객체(DTO)

메서드 호출 횟수를 줄이기 위해 프로세스 간에 데이터를 전송하는 객체

데이터 전송 객체

일반적으로 다수의 필드, getter, setter를 포함하는 단순한 구조를 가진다. 데이터 전송 객체(이하 DTO)는 네트워크 상에서 한 번의 호출로 많은 정보를 전송하기 위해 설계됐으며, 분산 시스템을 구현하는 핵심적인 개념이다.

원격 파사드와 비슷한 개념으로 DTO도 한동한 사용할 모든 데이터를 가져와야 한다. 원격 호출의 지연 비용을 감안할 때 여러번 추가로 호출하는 것보다는 필요 이상의 데이터를 한번에 전송하는 것이 낫다.

일반적으로 도메인 객체를 전송하기에는 힘든 경우가 많다.

  • 직렬화가 어려움
  • 모델 상 복잡한 연관관계
  • 필요없는 도메인 모델의 포함

그 대신 DTO를 통해서 도메인 객체에서 단순화된 형식의 데이터를 전송해야한다.

DTO는 특정 클라이언트의 필요성에 맞게 설계하는 것이 이치에 맞다.

  • DTO가 뷰(웹페이지, GUI)에 대응되는 경우가 많다.
  • 비슷한 DTO가 여러 화면에서 요구된다면 가능한 하나의 DTO로 통합하는 것이 좋다. (일반화 + 재사용성)

어떤 방식이 좋을까?

예를 들면 대부분의 상호작용에 특정한 DTO 하나를 사용하거 한두 개의 특정 요청과 응답에는 다른 DTO를 사용할 수 있다.

가능하다면 DTO를 단순하게 불변객체로 만드는 것도 좋다고 본다. 단 값객체(VO)와는 구별해야 한다. 중요한 것은 구조가 아니라 객체를 사용하는 목적이다.

DTO의 대안

범용 컬렉션 자료구조를 대안으로 사용하는 경우가 있다.

  • 배열 : 가독성을 해치므로 사용하지 마라
  • 딕셔너리(Map) : 그나마 나은 대안이긴 하나 아래와 같은 단점이 있다. (개인적으로 비추)
    • 명시적 인터페이스를 잃음 (DTO 특정 클래스가 없음)
    • 타입안정성을 잃음 (어떤 타입이라도 get, set이 가능해짐)

개인적으로 아주 특수한 상황을 제외하면 사용하면 안되는 안티 패턴으로 분류한다.

직렬화

  • 이진
  • 텍스트(xml, json 등)

중요한 것은 직렬화, 역직렬화가 가능하고 호환성과 플랫폼 이식성을 가지는 것이다.

도메인 객체와 DTO 간 조립

도메인객체와 DTO는 상호간 의존성을 가지는 것은 안 좋음

따라서 도메인 모델로 부터 DTO를 생성하고 DTO로 부터 모델을 업데이트 하는 별도의 어셈블러 객체를 만드는 것이 좋다.

어셈블러 객체를 사용해 도메인 모델과 데이터 전송 객체를 서로 독립적으로 유지할 수 있다.

개인적으로 DTO의 속성이 중요하지 행위가 중요하지 않으므로(정확하게 데이터만 클라이언트와 통신되면 됨) 도메인 객체에 의존하는 것은 괜찮다고 봄. DTO 생성자로 도메인 객체를 받아서 생성하거나 as 와 같은 메서드를 통해서 도메인 객체로 반환하는 것이 구현 편의성을 제공하는 것 같음. 특별하게 복잡한 경우가 아니면 조립(Assembler) 객체를 생성할 필요가 없다고 판단함.

MVC

사용자 인터페이스 상호 작용을 세 가지(모델, 뷰, 컨트롤러) 독립적인 역할로 분할한다.

Model

  • 모델은 도메인에 대한 정보 (도메인객체)
  • UI에 직접 사용되지 않는 모든 데이터와 동작을 포함하는 비시각적 객체
    • 트랜젝션 스크립트도 모델이라고 간주할 수 있다.

View

UI에서 모델을 표시

Controller

사용자로부터 입력을 받고, 모델을 조작하며, 뷰를 적절하게 업데이트 한다. UI는 뷰와 컨트롤러의 조합으로 작동한다.

분리

MVC의 핵심 개념은 프레젠테이션을 모델에서 분리하고, 컨트롤러를 뷰에서 분리하는 두가지 분리다.

1. 프레젠테이션에서 모델 분리

프레젠테이션 -> 모델로 단방향 의존

중요한 것은 모델 작업을 진행하면서 어떤 프레젠테이션을 사용하는지 알 필요가 없다는 것이다. 즉 프레젠테이셔은 유연하게 변경할 수 있다.

하지만 이벤트 통보(notify)라는 개념으로 모델에서 뷰로 의존이 가능하다.

2. 컨트롤러를 뷰에서 분리

어떤 프레임워크에서는 컨트롤러와 뷰를 분리하지 않기도 한다.

하나의 뷰 하나에 컨트롤러 2개를 사용하면 컨트롤러가 뷰에 대한 전략 역할을 한다. 예를 들면 뷰 하나를 수정과 입력 컨트롤러 사용한다. 그 반대로 컨트롤러가 복수개의 뷰를 분기해서 사용할 수 있다.

페이지 컨트롤러

웹 사이트에서 특정 페이지나 동작에 대한 요청을 처리하는 객체

일반적인 컨트롤러(@Controller)를 말한다.

단순하게 하나의 서버페이지를 사용할 경우 스크립트릿 코드(<% ... %>)를 사용할 수 있다. 하지만 헬퍼 객체를 사용해서 논리적 처리(Controller처리)를 담당하고 서버페이지(View)를 분리할 수 있다.

기본역할

  • 작업에 필요한 데이터 추출
  • 처리 : 모델 객체를 생성하고 호출. Request와 같은 요청객체 의존성이 없어야함
  • 뷰 결정 후 모델 전달

프런트 컨트롤러

웹 사이트의 모든 요청을 처리하는 컨트롤러

프런트 컨트롤러의 동작 방식

프론트 컨트롤러(Front Controller)는 모든 요청을 단일 처리기 객체로 집중하는 방법으로 요청을 통합 처리한다. 이 객체는 공통적인 동작을 수행하고 데코레이터(decorator)를 사용해 런타임에 수정할 수 있다.

인터셉터 필터

프런트 컨트롤러의 처리기를 래핑해 인증, 로깅, locale 식별 등을 처리하기 위한 필터 체인(필터의 파미프라인)을 만들 수 있다.

프론트 컨트롤러와 같이 사용하면 매우 유용함.

Spring MVC의 DispatcherServlet가 위 패턴을 바탕으로 개발됨

템플릿 뷰

HTML 페이지에 표시자를 삽입해 정보를 HTML로 렌더링한다.

HTML을 동적으로 표시한 부분과 정적인 부분(템플릿)으로 구성하고 모델을 동적 정보로 렌더링하는 것이다. 일반적으로 많이 사용하는 템플릿 엔진(velocity, freemaker, thymeleaf)이 해당 패턴으로 구현되어 있음

단점

  • 일반 디자이너들은 페이지 관리가 힘듬
  • 테스트하기가 어려움 (웹서버 의존)

변환 뷰

도메인 데이터 요소를 요소별로 변환하고 HTML로 변환하는 뷰

템플릿 뷰가 HTML을 기반으로 접근했다면 변환 뷰는 모델을 기반으로 접근함

2단계 뷰

도메인 데이터를 먼저 일종의 논리적 페이지로 변환한 다음 이를 다시 HTML로 변환하는 2단계 과정을 통해 HTML로 변환한다.

2번의 변환

  1. 데이터를 특정한 형식 지정 없이 논리적 프레젠테이션으로 모델링
  2. 논리적 프레젠테이션에 실제 필요한 형식(HTML)으로 변환
    1. 공통 룩앤필이나 레이아웃 지원

논리적 프레젠테이션(논리적 화면)

  • 필드, 머리글, 꼬리글, 테이블, 옵션 등을 포함할 수 있다.
  • 프레젠테이션 기반 모델은 다양한 위젯과 데이터를 포함하지만 HTML 외형을 지정하지 않는 모델이라고 생각하면 된다.

구현방법

  1. XSLT
  2. 클래스 분리
    1. 테이블 클래스, 행 클래스 등 : ex) 닷넷 웹폼 구성요소 클래스
    2. render 메소드로 HTML 변환

어플리케이션 컨트롤러

화면 이동(뷰 선택)과 애플리케이션 흐름(도메인 로직)을 처리하는 중심 지점

애플리케이션 컨트롤러

같은 종류의 컨트롤러가 특정 조건에 반복되는 경우가 있는데, 이를 해결하기 위함이다. 예를 들면 회원가입 시 특정 상태 부가정보 입력 분기 시

입력 컨트롤러는 애플리케이션 컨트롤러로부터 모델을 대상으로 실행할 명령과 애플리케이션의 상태에 따라 사용할 올바른 뷰에 대한 정보를 얻을 수 있다.

작동원리

  • 도메인 논리 선택
  • 뷰 선택

사용시점

객체의 상태에 따라 다른 뷰를 표시하며 페이지를 표시하는 순서에 대한 명확한 규칙이 있을 떄

애플리케이션 흐름이 변경될 때 여러 곳에서 비슷한 변경이 필요하다면 애플리케이션 컨트롤러가 필요하다는 신호