Isolating the Domain: Introducing the Applications


도메인의 책임이 시스템의 다른 부분과 뒤섞이는 것을 방지하기 위해서, 도메인 레이어를 표시하는 Layered Architecture를 적용해 보자.
깊은 분석 없이도, 3가지 user-level application function을 명시할 수 있고, 3개의 어플리케이션 레이어 클래스에 할당할 수 있다.
1. 특정 화물(Cargo)에 대한 과거와 현재 처리에 접근할 수 있는 Track query.
2. 시스템에 New Cargo를 등록하고 준비할 수 있는 Booking Application.
3. 각 화물(Cargo)의 처리를 기록할 수 있는 Incident Logging Application. (Tracking Query에서 검색한 정보를 제공)

Distinguishing ENTITIES and VALUE Objects

(Entity와 Value Object의 구분)

Customer
Customer는 명백하게 사용자를 나타내는 Indentity를 가지고 있으므로 모델에서 Entity이다.

Cargo
2개의 동일한 상자가 구분되어야 하기 때문에 Cargo 객체는 Entity이다. 실제로 화물 회사에서는 각 화물에 tracking ID를 부여한다.
이러한 ID는 자동으로 생성되어 user에서 보여지고, 이 경우에는 booking time(화물을 부칠 때)시 customer에게 전달 될 것이다.

Handling Event and Carrier Movement
무슨 일이 일어나고 있는지 추적하기 위해서 개별 incident에 주의를 기울인다.
실제 세상의 이벤트를 반영하고, 일반적으로 교환가능하지 않다. 따라서 Entity에 해당한다. 개별 Carrier Movement는 수화물 선적 스케쥴에서 얻은 코드로 명시될 수 있다.
Handling Events는 Cargo ID, Completion Time, Type의 조합으로 유일하게 명시될 수 있다. 왜냐하면, Same Cargo는 동시에 선적되고, 하선될 수 없기 때문이다.

Location
동일한 이름을 가진 2개의 장소는 동일한 장소가 아니다. 위도와 경도가 유일한 키로 제공될 수 있지만, 실용적이지 않기 때문에 이 시스템에서 대부분의 목적에 적합한 측정값이 아니다. Location은 수화물 선적 항로와 다른 도메인 특화 개념에 따른 장소와 관련되어 있는 지정학적 모델의 일부이다. 따라서 임의의, 내부적인, 자동으로 생성된 identifier가 제공될 것이다.

Delivery History
까다로운 항목이다. Delivery History는 교환가능하지 않기 때문에 Entity이다. 그러나 Delivery History는 Cargo와 1:1 관계성을 갖기 때문에 실제로는 그 자체로 identity를 가지지 않는다. Delivery History의 identity는 Cargo의 것을 빌려온다. 모델을 Aggregate로 할 때 더 분명해진다.

Delivery Specification
비록 Delivery Specification이 Cargo의 목적지를 의미하지만, 이 추상화는 Cargo에 달려 있다. Delivery Specification은 사실은 어떤 Delivery History의 가상의 상태를 표현한다. Cargo에 첨부된 Dilivery History가 점차적으로 Cargo에 추가된 Delivery Specification을 만족시킬 것으로 기대한다. 만약 동일한 장소에 2개의 Cargo를 가지고 있다면, 동일한 Delivery Specification을 공유하지만, 동일한(empty)로 히스토리로 시작하는 경우일지라도, 동일한 Delivery History를 공유하는 것은 아니다. Delivery Specification은 Value Object이다.

Role and Other Attributes
Role은 자격을 얻는 연관성(association)에 대해서 이야기하고 있지만, 히스토리나 연속성은 없다. Role은 Value Object이고, 다른 Cargo/Customer 연관성 사이에 공유할 수 있다. 타임스탬프나 이름과 같은 다른 속성은 Value Object이다.

Designing Associations in the Shipping Domain


Original Diagram에 있는 연관성 중에 어느 것도 지정된 횡방향이 없지만, 설계에서 양방향 연관성은 문제가 있다. 또한 횡방향은 종종 도메인에 대한 통찰력(insight)을 포착하여 모델 그 자체를 심화시킨다.

Customer가 선적된 모든 Cargo에 대해 직접적인 참조를 가지고 있는 경우에는, 장기간 반복되는 Customer이 성가셔 할 것이다. 또한 Customer의 개념은 Cargo에 특화된 것이 아니다. 커다란 시스템에서는, Customer가 많은 객체로 작업하는 역할을 가지게 될 것이다. 그러한 특정 책임에 대한 자유를 주는 것이 가장 좋은 방법이다. Customer가 Cargo를 발견하는 능력을 필요로 하는 경우, 데이터베이스 쿼리를 통해서 작업할 수 있다.

어플리케이션이 선박의 재고(inventory)를 추적하는 경우에는 Carrier Movement에서 Handling Event로의 이동이 중요하다. 하지만, 비즈니스에서는 단지 Cargo가 필요할 뿐이다. Handling Event에서 Carrier Movement로 연관성을 이동가능하게 하는 것은 비즈니스에 대한 이해를 획득하는 것이다. 다중성을 가진 방향성을 허용하지 않기 때문에 단순한 객체 참조로 구현을 줄일 수 있다.

AGGREGATE Boundaries


Customer, Location, Carrier Movement는 고유한 Identity를 가지고, 많은 Cargoes에서 공유하기 때문에 자신의 속성과 여기서 논의하는 상세 레벨(detail level) 아래에 있을 수 있는 다른 객체들을 포함하는 자체 Aggregate의 root가 되어야만 한다.
Cargo는 또한 분명한 Aggregate root이지만, boundary를 그릴 때에는 약간 생각이 필요하다.

Cargo Aggregate는 존재하지 않는 모든 것을 쓸어 담을 수 있지만, 특정 Cargo에는 Delivery History와 Delivery Specification, Handling Event를 포함할 수 있다. Delivery History는 Cargo ID를 통해서 조회될 수 있으므로, Cargo Boundary내부에 존재하는 것이 적합하다. Delivery Specification(배송 명세서)은 Value Object이므로 Cargo Aggregate내에 포함되는 것에 문제가 안 된다.

Handling Event는 또다른 문제이다. 앞에서 이것을 검색할 수 있는 2가지 데이터베이스 쿼리에 대해서 고려했다. 하나는 Cargo Aggregate내에 오컬이 되는 Collection에 대한 가능한 대안으로써 Delivery History에 대해 Handling Event를 찾는 것이다. 다른 하나는 특정 Carrier Movement에 대해 로드(load)하고 준비할 모든 동작을 찾도록 사용하는 것이다. 두번째 경우에, Cargo를 처리하는 활동은 Cargo 그 자체와 별개로 고려될 때도 의미가 있어 보인다. 따라서 Handling Event는 그 자체로 Aggregate의 Root가 되어야 한다.

사용자 삽입 이미지

[그림 7-1] Aggregate Boundary

Selecting REPOSITORIES


설계에는 Aggreate root가 5개 Entity로 존재한다. 따라서 Repository를 가질 수 있도록 허용된 다른 객체가 없기 때문에 고려 대상을 제한할 수 있다.
실제로 이러한 Entity들 중에서 어느 것이 Repository를 가져야 하는지 결정하기 위해서는, 어플리케이션 요구사항으로 돌아가야만 한다. 예약 어플리케이션(Booking application)을 통해서 예약하기 위해서 사용자는 여러 역할을 수행할(수화물 배송자(shipper), 수화물 수취인(receiver), 등) Customer(s)를 선택해야 한다. 따라서 Customer Repository가 필요하다. Cargo에 대한 목적지를 명시하기 위해서 Location을 찾을 수 있는  Location Repository를 생성해야 한다.

Activity Logging Application은 사용자가 Cargo가 적재되고 있는 Carrier Movement를 찾을 수 있어야 하므로 Carrier Movement Repository가 필요하다. 사용자는 Cargo가 어느 시스템에 로드되었는지 알려야 하므로 Cargo Repository도 필요하다.

사용자 삽입 이미지

[그림 7-2] Repository

여기서 첫번째 iteration에서 Delivery History와의 연관성을 Collection으로 구현하기로 결정했고, Carrier Movenent로 무엇이 로드되었는지를 찾을 필요가 없기 때문에 Handling Event Repository는 없다. 이러한 이유중 하나가 만약에 바뀐다면, Repository를 추가해야 한다.


Walking Through Scenarios


이러한 모든 결정을 상호 체크하기 위해서 지속적으로 어플리케이션 문제를 효율적으로 해결할 수 있는 시나리오를 통해 단계를 밟아야 한다.

Sample Application Feature: Changing the Destination of a Cargo

경우에 따라서 Customer는 "화물을 Haceksack로 보내달라고 했지만, 사실은 Hoboken으로 보내야 합니다"라고 말할 수 있는데, 시스템에서는 이러한 변경을 반영할 수 있어야 한다.

Delivery Specification은 Value Object이기 때문에, 간단히 버리고 새로운 것을 얻을 수 있다. 따라서, Cargo는 setter method를 통해서 변경할 수 있다.

사용자는 동일한 Customer의 반복되는 예약은 유사한 경향이 있기 때문에 이전 Cargo를 새로운 것에 대한 프로토타입으로 사용하고 싶어 한다. 어플리케이션은 Repository에서 Cargo를 찾은 다음, 선택된 것을 기반으로 새로운 Cargo를 생성하는 명령을 선택할 수 있을 것이다. Prototype 패턴을 사용하여 설계할 것이다.

Cargo는 Eentity이고, Aggregate의 Root이다. 그러므로, 주의 깊게 복사해야 한다. Aggregate Boundary에 둘러싸여 있는 각 객체나 속성에 무슨 일이 일어날지 고려해야 한다.

각각에 대해서 살펴보자.
Delivery History: 이전 History가 적용되지 않기 때문에 새로운 비어있는 객체를 생성해야 한다. Aggregate Boundary내에 Entity가 있는 일반적인 경우에 해당한다.
Customer Roles: 새로운 수화물(shipment)에서 동일한 역할을 수행하게 될 수 있으므로, 키를 포함하고 있는 Customer를 참조하는 키를 가지고 있는 맵(Map, or Collection)을 복사해야 한다. 하지만, Customer 객체 자체를 복사하지 않도록 주의해야 한다. Aggregate Boundary 밖에 있는 Entity이기 때문에, 이전에 참조된 Cargo 객체와 동일한 Customer 객체에 대한 참조로 끝나야 한다.
Tracking ID: 처음부터 새로운 Cargo를 생성할 때처럼 동일한 소스에서 새로운 Tracking ID를 제공해야 한다.

모두 Cargo Aggregate Boundary 내부에서 모든 것을 복사했음에 유의하라. 복사본에 약간의 수정이 있을 수 있지만, Aggregate Boundary 바깥에는 전혀 영향을 미치지 않는다.

Object Creation


FACTORIES and Constructors for Cargo

Cargo에 대한 멋진 Factory를 가지고 있거나 "Repeat Business"시나리오에서처럼 Factory로 또다른 Cargo를 사용한다면, 여전히 Primitive 생성자를 가져야 한다.
생성자가 불변성(invariant)을 만족시키거나 최소한 Entity의 경우, Identity를 손대지 않는 객체를 생성하기를 원한다.

Factory에서 리턴하는 결과는 동일할 것이다. 비어 있는 Delivery History, 널 값을 가지는 Delivery Specfication을 가진 Cargo를 리턴한다.

Pause for Refactoring: An Alternative Design of the Cargo AGGREGATE

모델링과 설계는 지속적인 전진 과정이 아니다. 모델과 설계를 개선하는 새로운 통찰력(insight)을 얻기 위해서 잦은 리팩토링이 없으면 서서히 멈출 것이다. 비록 작업을 수행하고, 모델을 반영할지라도 지금까지, 이 설계에 몇 가지 까다로운 측면이 있다. 설계 시작할 때 중요하지 않게 보이는 문제들은 성가신 문제가 되기 시작할 것이다.

만약 동시에 몇몇 사용자가 Cargo를 수정하는 경우, Handling Event 트랜잭션은 실패하거나 지연될 것이다. Handling Event를 입력하는 것은 빠르고 쉽게 해야 하는 작업 활동(operational activity)이므로, 중요한 어플리케이션 요구사항은 경합없이 Handling Event를 입력할 수 있는 기능이다. 이것은 다른 설계를 고려하게 한다.

Handling Event의 Delivery History Collection을 쿼리로 대체하면 Aggregate 밖에서 무결성 이슈를 제기하지 않고 Handling Event를 추가할 수 있다.
이러한 변경은 간섭없이 트랜잭션을 완료할 수 있게 한다. 만약 입력되는 Handling Event가 많고, 상대적으로 쿼리수가 적으면, 이 설계는 더 효율적이 된다. 사실, 관계형 데이터베이스가 기본 기술일 경우, 쿼리를 사용하여 Collection을 에뮬레이션할 수 있다.

Collection보다 쿼리를 사용하는 것이 Cargo와 Handling Event 사이에 주기적 참조에 대해 일관성을 유지하는데 대한 어려움을 감소시킨다. 쿼리에 대한 책임을 지기 위해서 Handling Event에 대한 Repository를 추가한다.

Handling Event Repository는 특정 Cargo와 관련된 Event에 대한 쿼리를 지원한다. 게다가, Repository는 특정 질문에 대해 효율적으로 대답하기 위해 최적화된 쿼리를 제공할 수 있다. 예를 들면, 잦은 접근 경로가 마지막으로 보고된 로드 혹은 언로드를 찾는 Delivery History라면, Cargo의 현재 상태를 추론하기 위해서 관련된 Handling Event만을 리턴하도록 쿼리를 고안할 수 있다.
특정 Carrier Movement에 로드된 모든 Cargo를 찾는 쿼리를 원한다면, 쉽게 추가할 수 있다.


받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://www.yongbi.net/rss/response/826

Chapter Six. The Life Cycle of a Domain Object


모든 객체는 생명 주기(life cycle)를 가지고 있다. 다양한 상태를 거쳐서 객체는 태어나고, 일정기간 동안 보관되거나(archived) 삭제되면서 점차적으로 사라진다.

문제는 2가지 범주로 나뉜다.

1. 수명 주기(life cycle) 동안 무결성(integrity) 유지.
2. 수명 주기 관리의 복잡성으로 인해 모델이 수렁에 빠지는 것을 방지.

3가지 패턴을 통해서 이러한 이슈를 다룰 것이다.
첫째, AGGREGATES는 명확한 소유권과 경계를 정의하고, 객체가 혼란스럽고 그물처럼 얽힌 것을 피하면서 모델 자체를 강화한다.
이 패턴은 수명 주기 동안 모든 단계에서 무결성을 유지하는데 중요하다.
둘째, 복잡한 객체와 내부 구조 캡슐화를 유지하는 AGGREGATES를 생성하고 재구성하기 위해 FACTORIES를 사용하여 수명 주기 초기로 돌린다.
셋째, REPOSITORIES는 뒤얽힌 광대한 인프라스트럭쳐를 캡슐화하여 지속적인 객체(persistent object)를 찾고 검색하는 수단을 제공하고, 수명 주기의 중간 단계와 끝 부분을 처리한다.
비록, REPOSITORIES and FACTORIES는 도메인에서 나온 것은 아니지만, 도메인 디자인에서 의미있는 역할을 한다.

AGGREGATES는 수명 주기의 모든 단계에서 불변을 유지해야 하는 범위를 표시한다.
FACTORIES and REPOSITORIES는 특정 수명 주기의 복잡성을 캡슐화하여 AGGREGATES에 작용한다.

Aggregates

(집합)

하나의 독립된 객체에 대해 수정/삭제하고자 하는 요청이 동시에 발생했을 때, 순서를 보장해야 한다.
복잡한 연관성을 가진 모델에서 객체를 변경하는 것에 대한 일관성을 보장하는 것은 어렵다.
개별 객체 뿐만 아니라, 밀접하게 관련된 객체 그룹에 적용되는 불변성을 유지 관리해야 한다.
그러나 조심스런 잠금 계획(locking scheme)은 여러 사용자들이 서로를 무의미하게 간섭하게 하고 시스템을 사용할 수 없게 한다.

우선, 모델 내에서 참조를 캡슐화하기 위한 추상화가 필요하다.
AGGREGATE는 데이터 변경 목적을 위해 하나의 단위로 취급되는 연관된 객체의 클러스터이다.
각각의 AGGREGATE는 Root와 Boundary(경계)를 가지고 있다.
Boundary는 AGGREGATE안에 있는 것을 정의한다.
Root는 AGGREGATE에 포함되어 있는 특별한 단일 ENTITY이다.
Root는 단지 외부 객체가 참조할 수 있는 AGGRETATE의 구성원일 뿐이지만, Boundary내에 있는 객체들은 서로를 참조할 수 있다.
ENTITIES는 Root가 아닌 로컬 ID를 가지지만, 외부 객체가 Root ENTITY 컨텍스트 밖에서는 볼 수 없기 때문에 해당 ID를 AGGREGATE내에서 구별할 수 있어야 한다.

데이터가 변경될 때마다 유지되어야 하는 일관성 규칙(consistency rule)인 불변성(invariant)은 AGGREGATE의 구성원 사이의 관계를 포함한다. AGGREGATES를 채우는 모든 규칙은 항상 최신 상태로 유지되는 것은 아니다.
Event Processing, Batch Processing, 또는 다른 업데이터 메커니즘을 통해서 특정 시간 내에 다른 종속성을 해결할 수 있다.
그러나 AGGREGATE내 적용된 불변성은 각 트랜잭션의 완료와 함께 시행될 것이다.

ENTITIES와 VALUE OBJECTS를 AGGREGATES로 클러스터하고 각각의 경계를 정의하라(Define Boundary around each). 하나의 Entity를 선택하여 각 Aggregate의 Root가 되게 하고, Root를 통해서 경계 내부 객체에 대한 모든 접근을 통제하라.
외부 객체는 단지 Root만을 참조할 수 있도록 하라.
내부 구성원에 대한 일시적인 참조는 단일 동작 내에서만 사용할 수 있다.
Root가 접근에 대해서 통제를 하기 때문에, 내부를 변경하여 공격할 수 없다.
이러한 배열은 Aggregate에 있는 객체에 대한 모든 불변성과 모든 상태 변화에 따른 Aggregate의 불변성을 적용하는 것을 실용적으로 만든다.


Factories


객체나 전체 Aggregate를 만들 때, 내부 구조가 너무 복잡해지거나 너무 많이 드러날 경우, FACTORIES가 캡슐화를 제공한다.
객체의 생성은 그 자체로 주요한 작업이 될 수 있지만, 복잡한 조립(assembly) 작업은 생성된 객체의 책임으로는 적합하지 않다.
그러한 책임을 결합하는 것은 이해하기 어려운 꼴사나운 설계를 만들 수 있다.

When a Constructor Is All You Need

다음 환경에서 public constructor를 선호한다.
- 클래스는 타입이다. 클래스는 흥미로운 계층 구조의 일부가 아니고, 인터페이스 구현으로 여러 가지 형태(다형성)로 사용되지 않는다.
- 클라이언트는 STRATEGY를 선택하는 방법으로 구현을 고려한다.
- 객체의 모든 속성을 클라이언트에서 사용할 수 있기 때문에 클라이언트에 노출된 생성자 내부에 객체 생성이 중첩(nested)되지 않는다.
- 생성은 복잡하지 않다.
- public 생성자는 FACTORY와 동일한 규칙을 따라야 한다. 생성된 객체의 모든 불변성을 만족시키는 원자 연산(automic operation)이어야 한다.

다른 클래스의 생성자 내에서 생성자를 호출하는 것을 피하라.
생성자는 정말로 간단해야 한다.
복잡한 구성, 특히 Aggregate가 그렇게 복잡할 때, Factory를 요구한다.

Designing the Interface

독립형이든 FACTORY METHOD를 사용하든 상관없이 FACTORY의 method 서명을 설계할 때는 2가지 사항을 염두에 두어야 한다.
- 각 동작은 원자적이여야 한다. (must be automic) FACTORY와 단일 상호작용으로 완전한 생산품을 만드는데 필요한 모든 것을 전달해야 한다. 또한, 일부 불변성이 만족스럽지 않은 경우, 생성이 실패했을 때 발생할 수 있는 것이 무엇인지 결정해야 한다. Exception이나 Null을 던질 수 있다. 일관성을 유지하기 위해서, FACTORIES의 실패에 대한 코딩 표준을 채택하는 것을 고려하라.
- FACTORY는 인자(arguments)에 연결될 것이다. 입력 변수를 신중하게 선택하지 않는다면, 난잡한 의존성을 만들 수 있다. 연결성 정도는 인자로 무엇을 하는지에 달려 있다. 생산품에 간단히 연결된다면, 적당한 의존성을 가질 수 있지만, 만약 생성에 사용하기 위해서 인자 중에서 일부를 고른다면, 연결 정도는 더욱 단단해진다.

Repositories


연관성(associations)을 통해서 다른 것과의 관계를 기반으로 객체를 찾을 수 있다. 그러나 생명 주기(life cycle) 중간에 ENTITY나 VALUE를 가로지르는 출발점이 있어야 한다.
객체를 통해서 무엇인지를 하기 위해서는 객체에 대한 참조를 가지고 있어야 한다.
그 참조를 어떻게 얻을 수 있는가? 한가지 방법은 생성 작업이 새로운 객체에 대한 참조를 반환하기 때문에 객체를 생성하는 것이다.
두번째는 연결성(association)을 횡단(traverse)하는 것이다. 이미 알고 있는 한가지 객체에서 시작하여 연관된 객체를 요구한다.
세번째 방법은 관계형 데이터베이스(RDBMS)를 이용하는 것이다. 쿼리를 통해서 데이터베이스에 있는 속성 기반으로 객체를 찾을 수 있다.


클라이언트는 기존 도메인 객체에 대한 참조를 얻는 실제적인 수단이 필요하다. 만약 인프라스트럭처를 통해서 쉽게 구현할 수 있다면, 클라이언트 개발자는 모델을 진흙탕으로 만들면서 더 많은 연관성(association)을 추가할 것이다. 다른 한편으로는 데이터베이스에서 필요한 정확한 데이터를 가져오기 위해서 쿼리를 사용하거나, Aggregate Root를 탐색하기보다는 일부 특정 개체를 가져오기 위해서 쿼리를 사용할 수 있다. 도메인 로직은 쿼리와 클라이언트 코드로 이동하고, ENTITIES와 VALUE OBJECTS는 단지 데이터 컨테이너가 된다. 대부분의 데이터베이스가 인프라스트럭처에 접근하기 위해 적용하는 기술적인 복잡성으로 인해 클라이언트 코드가 빠르게 변하기 때문에, 개발자들은 도메인 레이어를 바보 취급하고 모델과는 아무런 상관이 없게 된다.

영속하는 객체(Persistent Object)는 객체의 속성에 기반한 검색을 통해서 전역으로 접근가능해야 한다.
그러한 접근은 횡단을 통해서 도달하기 쉽지 않은 Aggregate의 Root에 필요하다.
일반적으로는 ENTITIES이고, 때때로 복잡한 내부 구조를 가지고 있는 VALUE OBJECTS이고, 가끔은 열거된 값을 가지는 VALUES이다.
다른 객체에 대한 접근을 제공하면, 중요한 차이점이 없어진다.
자유로운 데이터베이스 쿼리는 실제로 도메인 객체와 Aggregate 캡슐화를 위반할 수 있다.
기술적인 인프라스트럭처와 데이터베이스 접근 메커니즘에 대한 노출은 클라이언트를 복잡하게 만들고, MODEL-DRIVEN DESIGN을 모호하게 한다.

REPOSITORY는 개념적인 집합(보통 모방하여)으로 특정 타입에 대한 모든 객체를 표현한다. REPOSITORY는 더 정교한 쿼리 능력을 제외한 collection처럼 동작한다.
적절한 타입의 객체가 추가되거나 삭제되면, REPOSITORY 뒤에 있는 기계는 데이터베이스에 객체를 삽입하거나 삭제한다.
클라이언트는 일반적으로 특정 속성값인 클라이언트에 의해 지정된 규격에 기반한 객체를 선택하는 쿼리 메소드(query method)를 사용하여 REPOSITORY에 객체를 요청한다. REPOSITORY는 데이터베이스 쿼리와 메타데이터 맵핑에 대해 캡슐화된 기계작업에 의해 요청된 객체를 조회한다.
REPOSITORY는 클라이언트가 요청한 규격이 무엇이건 간에 그 규격에 기반한 객체를 선택하는 다양한 쿼리를 구현할 수 있다.
또한, 해당 규격을 만족시키는 인스턴스의 숫자와 같은 요약 정보도 제공할 수 있다. 특정 숫자 속성에 대해서 일치하는 객체의 전체 숫자와 같은 요약 계산 정보도 제공할 수 있다.

글로벌 액세스가 필요한 객체의 각 타입에 대해서 해당 모든 객체의 in-memory collection에 대한 환상을 제공할 수 있는 객체를 생성하라.
잘 알려진 글로벌 인스턴스를 통해서 액세스를 제공하라. 데이터 저장소에 실제로 데이터를 입력하거나 삭제하는 기능을 캡슐화한 객체를 추가/삭제할 수 있는 method를 제공하라. 어떤 규격에 따라 객체를 선택하는 method를 제공하고, 전체적으로 인스턴스화한 객체나 규격에 만족하는 속성 값을 가진 객체의 collection을 제공하는 method를 제공하여 실제 스토리지와 쿼리를 캡슐화하라. 직접적인 액세스가 필요한 Aggregate root에 대해서만 Repository를 제공하라. 모델에 기반한 클라이언트를 유지하고, Repository에 접근하도록 모든 객체 스토리지를 위임하라.

REPOSITORY 개념은 많은 상황에서 채택 가능하다. 구현 가능성은 다양하지만, 염두에 두어야 하는 몇 가지 항목은 다음과 같다.

- 타입을 추상화하라. REPOSITORY는 특정 타입의 모든 인스턴스를 "포함한다(contains)". 그러나, 각 클래스마다 하나의 REPOSITORY가 필요하다는 것을 의미하는 것은 아니다. 데이터베이스 기술의 다형성(polymorphism)에 대한 부족으로 인해 나타난 제약 조건들에 직면할 수 있다는 것을 염두에 두어야 한다.
- 클라이언트와의 분리에 대해 장점을 취하라. 클라이언트에서 직접적으로 호출하는 것에 비해서 REPOSITORY 구현을 더 자유롭게 변경할 수 있다. 다양한 쿼리 기술과 메모리에 객체를 캐싱하는 것, 영속 데이터 저장 전략을 언제라도 자유롭게 변경하여 성능에 대한 최적화를 할 수 있다.
- 클라이언트에 트랜잭션 컨트롤을 남겨 두라. 비록 REPOSITORY가 데이터베이스에 입력하고 삭제할지라도, 일반적으로는 어떤 것도 커밋(commit)하지 않는다. 클라이언트에서 컨텍스트를 완벽하게 초기화하고 작업 단위를 커밋한다.

The Relationship with FACTORIES

FACTORY는 객체의 생애(life)에 대한 시작(beginning)을 다룬다. REPOSITORY는 객체 생애의 중간과 마지막을 관리한다. 객체가 메모리에 있거나 Object Database에 저장되어 있을 때는 간단하다. 그러나 일반적으로 적어도 일부 객체는 RDB, File, Non object-oriented system에 저장된다. 그러한 경우에 데이터 탐색은 객체 형태로 재구성되어야 한다.

이 경우에 REPOSITORY는 데이터에 기반하여 객체를 생성하기 때문에 많은 사람들은, 사실 기술적인 관점에서, REPOSITORY를 FACTORY로 고려한다.
Domain-Driven Design관점에서 FACTORY와 REPOSITORY는 책임이 구별되어 있다. FACTORY는 새로운 객체를 생성하고, REPOSITORY는 이전의 객체를 찾는다.

받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://www.yongbi.net/rss/response/825

Chapter Five. A Model Expressed in Software

MODEL-DRIVEN DESIGN의 동력 손실 없이 구현을 하기 위해서는 기본에 대한 재구성이 필요하다.
모델과 구현을 연결하는 것은 상세한 수준에서 수행되어야 한다.

객체 사이의 연관성은 생각하고 그리고 쉽다. 하지만, 객체들을 구현하는 것은 잠재적인 난관이다.
연관성은 중대한 상세 구현 의사 결정이 MODEL-DRIVEN DESIGN의 실행 가능성과 관련하여 얼마나 중요한지를 보여준다.


하나의 패턴이나 다른 패턴을 명확하게 따르는 객체를 정의하는 것은 객체가 덜 모호하고, 견고한 설계를 위한 특정 선택을 할 수 있도록 길을 열어 준다. 그러면, 도메인 측면에서 객체로보다는 행동이나 동작으로 더 명확하게 표현된다.

객체 지향 모델링 전통(object-oriented modeling tradition)과는 약간 다르지만, 일부 Entity나 Value Object에 동작에 대한 책임을 강요하는 것보다 Service로 표현하는 것이 가장 좋다.

Service는 클라이언트 요청에 대해 수행되는 작업이다. 소프트웨어의 기술적인 레이어에는 많은 Service가 있다.
일부 활동(activity)이 소프트웨어가 수행해야만 하는 작업에 따라 모델링되었지만, 상태와 일치하지 않을 때도 또한 도메인에서 나타난다.

관계형 데이터베이스에 저장하는 것과 같이, 객체 모델의 순수성을 파괴해야만 하는 피할 수 없는 상황도 있다.
이런 지저분한 상황을 다루어야 할 때, 코스에 머무르기 위한 몇 가지 가이드라인이 있다.


마지막으로, Module에 대한 논의는 모든 설계 의사 결정이 도메인에 대한 통찰력에 의해 동기부여가 되어야 한다는 점을 납득하게 만들 것이다.
종종 기술적 측정 기준으로 생각되는 높은 응집력과 낮은 결합에 대한 아이디어는 개념 그 자체에 적용될 수 있다.
MODEL-DRIVEN DESIGN에서 Module은 모델의 일부이고, 도메인 내 개념을 반영해야만 한다.

Associations


모델링과 구현 사이의 상호 작용은 객체간의 연관성 때문에 특히 까다롭다.
모델에서 탐색 가능한 모든 연관성에 대해서 동일한 특성을 갖는 소프트웨어어 메커니즘이 있다.
고객과 영업 담당자간의 연관성을 보여주는 모델이 두 가지에 해당한다.
한편으로는 두 사람 사이에 관련있다고 여겨지는 개발자들의 관계를 추상화한다.
다른 한편으로는 두 Java 객체 사이의 객체 포인터나 데이터베이스 조회의 추상화, 또는 일부 유사한 구현에 해당한다.

가능한 관계를 제한하는 것이 중요하다.
양방향 연관성은 두 객체가 같이 있을 때만 이해될 수 있음을 의미한다.

Entities (a.k.a. Reference Objects)


소프트웨어 시스템에서 잘못붙여진 Identity의 경우에 데이터 손상 및 프로그램 오류를 야기시킨다.
여기에 특별한 기술적인 문제들이 있지만, 기본적인 몇 가지 이슈만 살펴보면 다음과 같다.
많은 것들이 속성이 아니라 Identity로 정의되어 있다.
보통 사람은 출생에서 죽음과 그 이상으로 확대되는 Identity를 가지고 있다.
그 사람의 육체적인 속성은 변형되고, 궁극적으로 사라진다.
이름은 변경되고, 재정적인 관계는 왔다 갔다 할 것이다.
변경할 수 없는 개인의 단일 속성은 없다.
하지만, Identity는 지속된다.
나는 5살 때와 동일한 사람인가?
이러한 종류의 형이상학적 질문은 효과적인 도메인 모델을 찾는데 중요하다.
잠깐 재언급해보자 : 만약 내가 5살 때와 동일한 사람이라면, 어플리케이션 사용자가 주의를 기울이는가?

은행 어플리케이션에서 트랜잭션을 고려해 보자.
동일한 날에 동일한 계좌로 동일한 금액을 두번 입금하는 경우에도 트랜잭션을 구분할 수 있고, Identity를 가진 Entity이다.
반면에 두 거래 금액 속성은 아마도 일부 돈 객체의 인스턴스이다. 이러한 값들은 구분하는데 유용하지 않으므로 Identity를 가지지 않는다.

사실, 두 객체는 동일한 속성을 가지지 않아도 동일한 Identity를 가질 수 있다. 또는 반드시 동일한 클래스에 속해야 할 수 있다. 은행 고객이 은행 입출금내역서의 거래를 수표 레지스트리의 거래와 일치시킬 때, 심지어 다른 사람에 의해 다른 날짜에 기록되었을지라도, 특히 동일한 Indentity를 가진 거래와 일치해야 한다.   

수표 번호의 목적은 문제가 컴퓨터 프로그램으로 처리되든, 사람 손으로 처리되든 관계없이 이러한 목적으로 유일한 identifier를 제공하는 것이다.

객체가 속성이 아닌 Identity로 구분될 때, 모델 내 그것을 최우선으로 정의한다.
클래스의 정의를 단순하게 유지하고, life cycle의 연속성과 identity에 중점을 둔다.
양식이나 기록에 상관없이 각 객체를 구별하는 방법을 정의한다.

속성과 일치하는 객체를 호출하는 요구사항에 대해서 주의하라.
각 객체에 대한 유일한 결과를 생성하는 것을 보장하는 동작을 정의한다.
고유하게 보장되는 심볼을 추가할 수도 있다.

이 식별 방법은 외부에서 나올 수도 있고, 시스템에서 생성된 임의의 식별자일수도 있지만, 모델의 ID구분과 일치해야 한다.
모델은 같은 것을 의미하는 것을 정의해야만 한다.

Modeling ENTITIES


객체를 모델링할 때, 속성에 대해 생각하는 것이 자연스럽고, 그 동작에 대해 생각하는 것이 매우 즁요하다.
그러나 ENTITIES의 가장 기본적인 책임은 행동이 명확하고 예측 가능하도록 연속성을 확립하는 것이다.
속성이나 동작에 초점을 맞추기보다 ENTITIES 객체의 정의를 추려내서 시스템 내 유일하게 되도록 제한한다.
객체의 속성으로 이루어진 참된 유일한 키가 없을 경우, 또다른 일반적인 해결책은 클래스 내 유일한 심볼(숫자나 문자열과 같은)을 각 인스턴스에 부여하는 것이다.

이 ID 심볼이 한번 생성되어 ENTITY 속성으로 저장되면, 불변으로 지정된다.
심지어 개발 시스템이 이 규칙을 직접 싱행할 수 없는 경우일지라도 결코 변경하지 않아야 한다.
예를 들면, ID 속성은 객체가 데이터베이스로 병합되고, 재구성될 때 보존된다.

때때로 기술적인 Framework는 이 과정에 도움이 되지만, 그렇지 않은 경우에 엔지니어링 규칙이 적용된다.
종종 ID는 시스템에 의해 자동으로 생성된다. 생성 알고리즘은 시스템 내에서 유일함을 보장해야 한다.
동시 처리와 분산 시스템에서 문제가 될 수 있기 때문이다.

여기서 목표는 개발자가 문제를 해결하고 핵심 영역에 대한 우려를 어떻게 좁히는지를 알도록 고려할 부분을 지적하는 것이다.
핵심은 ID가 모델의 특정 측면에 돌쩌귀와 관련되어 있음을 인식하는 것이다. 종종 Identification의 수단은 도메인에 대한 주의깊은 연구를 요구한다.

ID가 자동으로 생성되면, 사용자는 그것을 결코 볼 필요가 없다. 사람 이름으로 기록을 찾도록 하는 연락처 관리 어플리케이션에서처럼, ID는 내부적으로만 필요하다. 프로그램에서는 정확하게 동일한 이름을 가진 2명의 연락처를 간단하고 분명한 방법으로 구분할 수 있어야 한다.

유일한 내부 ID를 통해서 시스템은 이를 수행할 수 있다.
2개의 서로 다른 아이템을 검색한 후, 시스템은 사용자에게 2개의 개별 연락처를 보여줄 것이다. 그러나 ID는 보이지 않는다. 사용자는 회사, 위치, 등등을 기준으로 구분할 수 있다.

마지막으로, 생성된 ID가 사용자에게 중요한 경우도 있다.
소포 배달 서비스를 이용해 패키지를 발송할 때, 회사의 소프트웨어에서 만들어진 Tracking Number(추적 번호)가 제공되고, 패키지를 추적하고 식별하는데 사용할 수 있다.

항공권 티켓과 호텔 예약할 때, 거래에 대한 유일한 식별자인 Confirmation Number(확인 번호)가 제공된다.
어떤 경우에는 ID의 유일함이 컴퓨터 시스템 범위를 넘어서서 적용되어야만 하는 경우도 있다.
예를 들면, 의학적 기록이 서로 분리되어 있는 컴퓨터 시스템을 가지고 있는 2개의 병원 사이에 상호교환 된다면, 이상적으로는 각 시스템이 동일한 환자 ID를 사용하겠지만, 각자의 심볼을 생성하는 경우에는 어렵게 된다.
그러한 시스템들은 종종 다른 기관, 일반적으로 정부관계기관 에서 발행된 식별자를 사용한다.
미국에서는 Social Security Number(사회보장번호)를 사용하여 사람을 식별한다.
그러한 방법은 누구나 이용할 수 있는 방법이 아니다. 모든 사람이 Social Security Number를 가지고 있는 것도 아니고 (특히 아이들이나 미국에 거주하지 않는 사람), 많은 사람들이 사생활(privacy) 이유로 사용하는 것을 반대한다.

덜 비공식적인 상황(비디오 대여)에서 전화번호가 식별자로 사용된다. 그러나 전화번호는 공유될 수 있다. 변경될 수도 있다. 예전 번호가 심지어 다른 사람에게 주어질 수도 있다.

이러한 이유로, 특별하게 부여된 식별자가 종종 사용된다. (항공사의 상용 고객 번호와 같은). 그리고 전화번호나 Social Security number와 같은 다른 속성들은 일치하는지 확인하고 검증하는데 사용된다. 어떤 경우에는 어플리케이션이 외부 ID를 필요로 할 때, 시스템의 사용자는 유일한 ID를 제공해야할 책임이 있고 시스템은 예외 처리를 위한 적절한 도구를 제공해야 한다.
주어진 모든 기술적인 문제들을 감안할 때, 주어진 근본적 개념 문제를 놓치기 쉽다.

두개의 객체가 동일한 것이라는 것이 무엇을 의미하는가?
ID로 각 객체에 도장을 찍거나 2개의 인스턴스를 비교하는 동작을 작성하는 것은 쉽지만, 이러한 ID들이나 동작들이 도메인에서 의미있는 구분에 따르지 않는다면, 더 혼란스럽게 할 뿐이다.

Value Objects


많은 객체에는 개념적인 ID가 없다. 이러한 객체들은 어떤 것의 특징을 설명한다.
모델에서 가장 눈에 띄는 객체는 일반적으로 ENTITIES이고, 각 ENTITY의 Identity를 추적하는 것이 중요하기 때문에 모든 도메인 객체에 Identity를 할당하는 것을 고려하는 것이 좋다. 사실, 일부 Framework는 모든 객체에 유일한 ID를 할당한다.
시스템은 모든 추적에 대해 대처 가능해야 하고, 가능한 많은 성능 최적화가 배제된다.

의미있는 ID를 정의하고, 분산 시스템이나 데이터베이스 스토리지에서 객체를 주적할 수 있는 확실한 방법을 찾기 위해서 분석 작업이 요구된다.

마찬가지로 중요한 것은, 인위적인 ID를 취하는 것은 잘못된 것이라는 것이다. 그것은 모델을 혼란스럽게 하여 모든 객체를 같은 틀에 집어 넣는다.

ENTITIES의 Identity를 추적하는 것은 필수적이지만, 다른 객체에 Identity를 덧붙이면 시스템 성능을 훼손하고, 분석작업이 추가되고, 모든 객체가 동일하게 보이도록 하여 모델을 혼란스럽게 할 수 있다.
소프트웨어 설계는 복잡성과의 끊임없는 싸움이다. 단지 필요할 경우에만 특별한 취급이 적용되도록 구분을 해야한다.

Value Object는 ENTITIES에 대한 정보를 제공한다.

Designing VALUE OBJECTS


VALUE OBJECTS가 어떤 인스턴스인지는 신경쓰지 않는다. 이러한 제약이 없기 때문에 단순하게 설계하거나 성능을 최적화할 수 있는 설계 자유가 제공된다. 복사, 공유, 불변성에 대한 선택을 포함한다. VALUE의 구현이 변경가능하게 되어야 한다면, 공유되지 않도록 해야 한다. 공유 여부와는 상관없이, 가능하면 VALUE OBJECT를 변경할 수 없도록 설계하라.

VALUE OBJECT를 정의하고, 불변하도록 지정하는 것은 일반적으로 다음과 같은 규칙을 따르는 경우이다.
- 모델에서 불필요한 제약을 피하여 개발자가 순수하게 기술적 성능 튜닝을 자유롭게 하도록 한다.
- 필수적인 제약 조건들을 명시적으로 정의하여 개발자가 의미있는 동작을 변경하지 않고, 설계를 조정할 수 있다.
- 그러한 설계는 종종 특정 프로젝트에서 사용되는 기술에 매우 특효가 있다.

Examples
Tuning a Database with VALUE OBJECTS


가장 낮은 수준의 데이터베이스는 디스크의 물리적 위치에 데이터를 위치시켜야 하고, 물리적인 부품이 이동하고, 데이터를 읽는데 시간이 걸린다.
정교한 데이터베이스는 이러한 물리적 주소를 클러스터링하여 하나의 물리적 작업으로 디스크로부터 연관된 데이터를 가져올 수 있다.
객체가 많은 다른 객체들로부터 참조된다면, 그 중 일부는 가까이 위치하지 않을 것이다(동일 페이지 상에서). 따라서 데이터를 가져오는데 추가적인 물리적 작업이 필요하다.
동일 인스턴스로 참조를 공유하는 것보다 복사본을 만들어서 많은 Entity의 속성으로 동작하는 VALUE OBJECT를, 그 Value Object를 사용하는 각 Entity와 동일한 페이지에 저장할 수 있다.
동일한 데이터를 여러 번 복사하여 저장하는 기법은 비정규화(denormalization)라고 하고, 종종 접근 시간(access time)이 스토리지 공간이나 유지보수의 단순성보다 더 중요할 때 사용된다.

별도의 테이블에 대한 연결(association)을 만드는 대신에, 관계형 데이터베이스에서 특정 Value를 Entity를 소유한 테이블에 넣을 수 있다.
분산 시스템에서는 다른 서버에 있는 Value Object에 대한 참조를 보유하는 것은, 메시지에 대한 응답 속도를 느리게 할 것이다.
대신에, 전체 객체의 복사본을 다른 서버에 넘겨야 한다.

Value Object를 다루기 때문에 자유롭게 복사본을 만들 수 있다.

Services


어떤 경우에는, 가장 명확하고 실용적인 설계는 개념적으로 어떤 객체에도 속하지 않는 동작을 포함한다.
문제를 강요하기 보다는, 문제 공간의 자연스러운 윤곽을 따라갈 수 있고, 모델에 명시적으로 SERVICES를 포함할 수 있다.
ENTITY나 VALUE OBJECT에서 자연스러운 Home(집)을 찾을 수 없는 중요한 도메인 동작이 있다.
이 중 일부는 사물이 아니라 본질적으로는 활동(activity)이나 행동(action)이지만, 모델링 패러다임이 객체이기 때문에 어쨌거나 객체에 적합하게 만들려고 할 것이다.

이제 더 많은 공통적인 실수는 행동을 적절한 객체에 맞추는 것을 너무 쉽게 포기하고 절차적인 프로그래밍을 향해서 미끄러져 들어가는 것이다.
객체의 정의에 어울리지 않는 객체로 강제로 작업을 할 때, 객체는 개념적인 명확성을 잃어버리고 이해하기 어렵게 되거나 리팩토링된다.
복잡한 작업은 단순한 객체의 역할을 애매하게 감추어서 쉽게 수렁에 빠질 수 있다.
이러한 작업들은 종종 많은 도메인 객체와 함께, 객체를 조정하고, 객체가 실행되도록 하기 때문에 추가된 책임은 모든 객체에 의존성을 만들어 내고, 독립적으로 이해할 수 있는 개념을 얽히게 한다.

때때로 서비스는 모델 객체로 가장하고, 어떤 동작을 하는 것 이상의 의미는 없는 객체로 나타난다.
이 "행위자(doers)"는 "Manager"와 같은 이름으로 끝난다.
그것들은 호스트하는 작업을 넘어서 도메인 안에서 어떤 의미를 가지지 않고, 상태도 가지지 않는다.
여전히 최소한 이런 솔루션은 실제 모델 객체를 엄망으로 만들지 않고 이러한 구분되는 행동을 home(집)에 제공한다.

도메인의 일부 개념은 객체로 모델링하는 것이 자연스럽지 않다.
요구되는 도메인 기능을 Entity나 Value의 책임으로 강제하는 것은 모델 기반 객체(model-based object)의 정의를 왜곡시키거나, 의미없는 인공적인 객체들이 추가된다.

Service는 Entity나 Value Object가 하는 것처럼 상태를 캡슐화하지 않고, 모델에서 단독으로 동작하는 인터페이스로서 제공되는 작업이다.
Services는 기술적인 Framework에서 공통적인 패턴이다. 하지만, 도메인 레이어에도 적용될 수 있다.

Name Service는 다른 객체들과의 관계를 강조한다. Entities와 Value Object와는 다르게, Service는 클라이언트를 위해 무엇을 할 수 있는지에 대한 관점에서 정의된다.
Service는 Entity보다는 Activity(활동)을 위해서, 명사라기 보다는 동사로 이름지어지는 경향이 있다.
Service는 추상적이고 의도적인 정의를 가질 수 있다.
Service는 정의된 책임을 가져야 하고, 책임과 그것을 수행하는 인터페이스는 도메인 모델의 일부로 정의되어야 한다.
동작 이름은 Ubiquitous Language로부터 가져오거나 그것을 소개하는 이름이 되어야 한다.
파라미터와 결과는 도메인 객체여야 한다.
Service는 신중하게 사용되어야 한다. 그리고 모든 동작에 대해서 Entity와 Value Object를 제거할 수 없다.

훌륭한 서비스는 3가지 특징을 가지고 있다.
- 동작은 Entity나 Value Object의 자연스런 일부가 아니라 도메인 개념과 관련되어 있다.
- 인터페이스는 도메인 모델의 다른 요소들로 정의되어 있다.
- 동작은 상태를 관리하지 않는다. (operation is stateless)

상태를 관리하지 않는다는 것은, 클라이언트에서 특정 Service 인스턴스의 개별적인 히스토리에 대해 고려하지 않고, Service의 인스턴스를 사용할 수 있다는 것을 의미한다.

도메인에서 중요한 프로세스나 변환이 Entity나 Value Object의 책임이 아닐 경우에, Service로 선언된 독립형 인처페이스로 모델에 작업을 추가하라.
모델의 언어로 인터페이스를 정의하고, 동작에 대한 이름이 Ubiquitous Language의 일부인지 확인하라.
Service를 Stateless로 만들어라.

SERVICES and the Isolated Domain Layer
(서비스와 격리된 도메인 레이어)


이 패턴은 도메인에서 그 자체적으로 중요한 의미를 갖는 Service에 중점을 두지만, 물론 Service가 도메인 레이어에서만 사용되는 것은 아니다.
도메인 레이어에 속한 서비스와 다른 레이어에 속한 서비스를 구별하고, 구별을 명확하게 하기 위해서 책임 요소를 고려해야 한다.

문자적으로 논의된 대부분의 Service는 순수하게 기술적이고 인프라 스트럭처 레이어에 속한다.
고메인과 어플리케이션 Service는 이러한 인프라스트럭처 Service와 협업한다.

어플리케이션 Service와 도메인 Service를 구별하는 것이 더 어려울 수 있다.
어플리케이션 레이어는 Notification의 순서에 대해서 책임을 지고, 도메인 레이어는 처리량(threshold)이 임계 값에 도달했는지 결정하는 것에 대해 책임을 진다.

많은 도메인이나 어플리케이션 Service는 Entities와 Values의 집단위에 구축되어 실제로 무엇인가를 얻고자 하는 도메인의 잠재력을 조직화하는 스크립트처럼 동작한다.
Entities와 Value Objects는 종종 너무 세분화되어 있어서 도메인 레이어의 기능에 편리한 접근을 제공한다.
여기서는 도메인 레이어와 어플리케이션 레이어 사이에 매우 미세한 라인이 있다.
예를 들면, 뱅킹 어플리케이션이 분석할 수 있는 스프레드시트 파일로 거래를 변환하여 내보낼 수 있다면, 해당 내보내기는 어플리케이션 Service이다.
뱅킹 도메인에서는 "파일 형식"은 의미가 없고, 포함된 비즈니스 규칙도 없다.

다른 한편으로는 하나의 계좌에서 다른 계좌로 돈을 옮기는 기능은 도메인 Service이다. 왜냐하면, 중요한 비즈니스 룰(rule)을 내포하고 있고 (예를 들면, 계좌와 관련된 신용거래와 직불 거래(credit and debit)), "현금 이체(fund transfer)"는 의미있는 뱅킹 용어이기 때문이다. 이경우에는 Service는 저절로 그렇게 된 것이 아니다. 2개의 계좌(account) 객체에 대부분의 일을 하도록 요구했을 것이다. 하지만, 계좌 객체에 "이체(transfer)" 동작을 하도록 하는 것은 그 동작이 2개의 계좌와 일부 글로벌 규칙을 포함하고 있기 때문에 처리하기 곤란하다.

아마도 2개의 Entry에 Rule과 이체 이력을 더하여 나타내기 위해서 현금 이체(Fund Transfer) 객체를 만들고 싶어할지도 모른다. 그러나, 여전히 은행간 네트워크에서 Service를 호출하는 부분이 남아 있다. 게다가 대부분의 개발 시스템에서 도메인 객체와 외부 리소스 사이의 직접적인 인터페이스를 하는 것은 곤란하다. 결과적으로 Fund Transfer 객체를 리턴하는 모델에 대한 용어에 입력하는 FACADE로 그러한 외부 Service를 변장시킬 수 있다.

하지만, 우리가 가지고 있는 중개자가 무엇이건 간에, 심지어 그 중개자가 우리에게 속해 있지 않다고 하더라도, 그러한 Service들은 계좌 이체에 대한 도메인 책임을 수행한다.

Granularity
(세분화)


비록 이 패턴 토론이 Service로 모델링하는 개념 표현력을 강조했지만, 클라이언트를 entity, Value Object로부터 분리하는(decoupling) 것뿐만 아니라, 도메인 레이어의 인터페이스에 대한 세분화를 컨트롤하는 수단으로도 가치가 있다.
중간 정도의 세분화(medium-grained), stateless Service는 단순한 인터페이스 뒤에 중요한 기능을 캡슐화하기 때문에 대형 시스템에서 재상용하기 더 쉽다.
작게 세분화된 객체는 분산 시스템에서 비효율적인 메시징을 초래할 수 있다.

앞서 논의한 것처럼, 잘 세분화된 도메인 객체는 도메인 객체의 동작이 조정된 어플리케이션 레이어 내 도메인으로부터 지식의 누수가 있을 수 있다.
매우 상세한 상호 작용의 복잡성은 어플리케이션 레이어에서 처리되기 때문에 도메인 지식이 어플리케이션이나 User 인터페이스 코드로 살며시 들어가서 도메인 레이어에서 손실 될 수 있다.
사려 깊은 도메인 서비스 도입은 레이어간 선명한 라인을 유지하는데 도움이 된다.

이런 패턴은 클라이언트 제어와 다재다능함보다 인터페이스 단순성을 선호한다.
대형 혹은 분산 시스템에서 컴포넌트 패키징에 매우 유용한 중간 정도의 세분화 기능을 제공한다.

때로 Service는 도메인 개념을 표현하는 가장 자연스러운 방법이다.

Access to SERVICES


J2EE와 CORBA와 같은 분산시스템 아키텍처는 Service 사용에 대한 협약과 분산 및 접근 기능을 추가한 Service에 대한 특별한 Publishing(출판) 메커니즘을 제공한다.
그러나 그러한 framework는 프로젝트에서 항상 사용되는 것은 아니다.
심지어 사용되더라도, 동기부여가 단순히 논리적인 관심사 분리(logical separation of concerns)일 때 지나치게 될 수 있다.

Service에 접근을 제공하는 수단은 특정 책임을 부각시키는 설계 결정만큼 중요하지는 않다.
"행위자(doer)" 객체는 Service의 인터페이스 구현으로 만족스러울 수 있다.
간단한 Singleton은 접근을 제공하기 위해 쉽게 작성될 수 있다.
코딩 규칙(Coding Conventions)은 이런 객체가 의미있는 도메인 객체가 아닌, Service 인터페이스에 대한 전달 메커니즘에 불과하다는 것을 명확하게 할 수 있다.
정교한 아키텍처는 분산 시스템에 정말로 필요하거나 framework의 기능을 활용할 필요가 있는 경우에만 사용해야 한다.

Modules


Module은 오래전에 만들어진 설계 요소이다. 기술적인 고려사항들이 있찌만, 인지 과부하가 모듈화의 주요 동기부여 사항이다. Module은 사람들에게 2가지 모델 관점을 제시한다.
- 전체에 압도당하지 않고, Module 내 세부 사항을 알 수 있다.
- 내부를 상세히 몰라도 Module 사이의 관계를 알 수 있다.

도메인 레이어에서 모듈은 더 커다른 스케일의 도메인을 이야기하는 모델의 의미있는 부분으로 나타난다.

누구나 모듈을 사용하지만, 모델의 완전한 자격을 갖춘 부분으로는 거의 활용하지 않는다.
코드는 기술적인 아키텍처 측면에서부터 개발자들의 작업 할당에 이르기까지 모든 종류의 범주(Category)로 분류된다.
심지어 리팩토링을 많이 하는 개발자들조차도 프로젝트 초기에 염두에 둔 Module을 사용하여 스스로 만족하는 경향이 있다.
Module 사이에 낮은 결합력과 높은 응집력이 있어야 한다는 것은 사실이다.
결합과 응집에 대한 설명은 연관성과 상호작용에 대한 분산에 기반하여 기계적으로 판단할 수 있는 기술적인 메트릭처럼 들리게 하는 경향이 있다.
그러나 그것은 단지 Module로 분류되는 코드(Code)가 아니라 개념이다.
사람이 한번에 얼마나 많이 생각할 수 있는지에 대해서는 한계가 있다. (따라서 낮은 커플링-결합-이 필요하다.)
아이디어의 일관적이지 않은 파편들은 구분되지 않고 섞여 있는 수프처럼 이해하기 어렵다. (따라서 높은 응집력이 필요하다.)

낮은 결합과 높은 응집력은 개별 객체 못지 않게 모듈에도 적용하는 일반적인 설계 원칙이다. 하지만, 이러한 더 큰 단위의 모델링이나 설계에 중요하다.
모듈 사이의 낮은 결합은 비용을 최소화하고, 다른 모듈들과 상호 동작하는 참조를 최소화하여 하나의 모듈에 대한 내용을 분석가능하게 한다.

동시에, 훌륭한 모델 요소는 시너지를 내고, 잘 선택된 모듈은 특히 풍부한 개념적 관계로 모델 요소들을 묶는다.
연관된 책임들을 가진 객체에 대한 이러한 높은 응집력은 복잡성의 규모를 가진 사람의 마음을 쉽게 다룰 수 있게 하여 단일 모듈 내 전념하기 위한 모델링과 설계 작업을 가능하게 한다.


시스템의 이야기를 말하고 있 응집력 있는 개념을 포함하는고 Module을 선택하라.
이것은 Module 사이의 낮은 결합을 야기하지만, 그렇지 않은 경우에는 개념을 풀기 위한 모델을 변경하는 방법을 찾거나, 혹은 의미 있는 방법으로 요소들을 함께 모으는 Module의 기반이 될 수 있는 보다 상위의 개념을 찾아야 한다.
서로 독립적으로 이해하고, 추론할 수 있는 개념적 의미에서 낮은 결합을 추구하라.
고수준(high-level) 도메인 개념에 따라 파티션할 때까지 모델을 수정(refine)하고 코드와 일치하는 것이 잘 분리된 것이다.

Module 이름은 Ubiquitous Language의 일부분이 되게 하고, Module과 그 이름은 도메인의 통찰력을 반영해야 한다.

Agile MODULES


Module은 모델의 나머지와 함께 상호 진화해야 한다.
이것은 모델과 코드와 함께 Module을 올바르게 리팩토링하는 것을 의미한다.
하지만, 이러한 리팩토링은 자주 일어나지는 않는다.
Module을 변경하는 것은 광범위한 코드 업데이트를 필요로 하는 경향이 있다.
그러한 변경은 팀 커뮤니케이션에 지장을 줄 수 있고, 소스 코드 관리 시스템과 같은 개발 도구에 장애물을 던질 수 있다.
결과적으로, Module 구조와 이름은 종종 클래스가 수행하는 것보다 훨씬 더 초기의 모델  형태를 반영하게 된다.


The Pitfalls of Infrastructure-Driven Packaging
(인프라 스트럭처 기반 패키징의 함정)


기술 환경에 필수적이거나 실제적으로 개발을 도와주는 최소한의 기술 분할 규칙(technical partitioning rules)을 선택하라.
예를 들어, 복잡한 데이터 지속성 코드(persistence code)를 객체의 동작 측면과 분리하는 것은 리팩토링을 쉽게 할 수 있다.

다른 서버에 코드를 분산하기 위한 실제적인 의도가 없다면, 동일 객체가 아니라면, 모든 코드를 동일한 Module내의 동일한 개념적인 객체에 구현하라.
패키징을 사용하여 도메인 레이어를 다른 코드에서 분리하라.
그렇지 않으면, 모델과 디자인을 선택하는 것을 지원하는 방식으로 도메인 개발자가 도메인 객채를 패키징하도록 가능한 한 많은 자유를 남겨 두라.

모듈화는 설계가 더 커지고 복잡해짐에 따라 더 중요해진다.

도메인 모델의 각 개념은 구현 요소에 반영되어야 한다.
일부 도메인 Service와 구성 Module과 함께 entity, Value Object, 연관성은 구현과 모델 사이에서 직접적인 대응 지점이다.
구현에서 객체, 포인터, 검색 메커니즘은 직접적으로, 분명하게 모델 요소와 매핑되어야 한다.
만약 그렇지 않으면, 코드를 정리하고 다시 돌아가 모델을 변경하거나 둘 모두 수행해야 한다.

추가하려는 항목이 나타내는 개념과 밀접하게 연관이 없는 도메인 객체에 어떤 것을 추가하는 유혹에 저항하라.
이러한 설계 요소는 해야 할 일이 있다. - 모델을 표현해야 한다.
수행되어야 하는 다른 도메인 관련 책임과 시스템 작업에 필요한 관리되어야 하는 다른 데이터가 있지만, 그것들은 이러한 객체에 해당하지 않는다.


Modeling Paradigms

Why the Object Paradigm Predominates
(왜 객체 패러다임이 먼저 자리잡았는가?)


팀이 객체 패러다임을 선택한 많은 이유는 기술적인 것이 아니다. 심지어 객체의 고유한 가치 때문도 아니다.
하지만, 문밖에서 객체 모델링이 단순함과 정교함의 훌륭한 균형을 이루고 있기 대문이다.

만약 모델링 패러다임이 너무 수수께끼 같다면, 개발자가 그것을 마스터하기 충분하지 않기 때문에 잘못 사용할 것이다.
팀의 비기술적ㅇ니 구성원이 최소한 패러다임의 기초를 파악하지 못한다면, 모델을 이해하지 못하고 Ubiquitous Language가 없어진다.
객체 지향 설계(Object-Oriented Design)의 기본은 대부분의 사람들에게 자연스럽게 나타난다.
일부 개발자가 모델링의 미묘함을 놓치지만, 심지어 비기술자조차도 객체 모델에 대한 다이어그램을 따를 수 있다.

Nonobjects in an Object World
(객체 세상에서의 비 객체)


도메인 모델이 객체 모델이 되어야만 하는 것은 아니다. 예를 들어, 프롤로그에서 구현된 Model-Driven Designs에서 논리적인 규칙과 사실로 이루어진 모델도 있다.
모델 패러다임은 사람들이 도메인에 대해 생각하기 좋아하는 특정 방법을 다루기 위해서 고안되었다.
프로젝트에서 지배적인 모델 패러다임이 무엇이건 간에, 다른 패러다임에서 훨씬 더 쉽게 표현할 수 있는 도메인의 일부 영역이 될 수 있다.



 
받은 트랙백이 없고, 댓글이 없습니다.

댓글+트랙백 RSS :: http://www.yongbi.net/rss/response/824