domain driven design ch9

21
Ch.9 암시적인 개념을 명확하게 (Domain Driven Design) chois79 121029월요일

Upload: hyeonseok-choi

Post on 20-May-2015

453 views

Category:

Technology


4 download

TRANSCRIPT

Page 1: Domain driven design ch9

Ch.9 암시적인 개념을 명확하게(Domain Driven Design)

chois79

12년 10월 29일 월요일

Page 2: Domain driven design ch9

About This Chapter

• 심층 모델은 도메인에 대한 본질적인 지식을 간결하고 유연하게 표현하는 중심 개념과 추상화를 담고 있는 중요한 요소이다

• 심층 모델 생성 과정

• 도메인의 본질적인 개념을 모델로 표현

• 성공적인 지식 탐구와 리팩토링을 반복하며 정제

• 중요한 개념이 모델과 설계에 명확하게 인식되고 표현이 되었을 때 가능

• 그렇다면, 어떻게 중요한 개념을 모델과 설계 내에 명확히 표현되게 할것인가?

• 전통적인 방식: 요구사항 문서에 서술된 명사와 동사를 식별

• 명사 -> 객체 이름, 동사 -> 객체의 메서드

• 얄팍한 지식에 기반을 둔 투박하고 무의미한 모델인 경우가 대부분

• 도메인 내의 암시적인 개념을 명확히 인식하고 반영하는 것이 필요

12년 10월 29일 월요일

Page 3: Domain driven design ch9

개념 파헤치기

• 개발자는 잠재해 있는 암시적인 개념을 드러내는 단서에 민감해야 한다

• 언어에 귀를 기울여라

• 어색한 부분을 조사하라

• 모순점에 대해 깊이 고민하라

• 시도하고 또 시도하라

12년 10월 29일 월요일

Page 4: Domain driven design ch9

언어에 귀를 기울여라

• 도메인 전문가가 사용하는 언어에 귀 기울여라

• 복잡하게 뒤얽힌 개념들을 간결하게 표현하는 용어가 있는가?

• 선택한 단어를 더 적절하게 고쳐주는가?

• 특정 문구를 이야기할 때 도메인 전문가의 얼굴에서 곤혹스러운 표정이 사라지는가?

• 개발자와 도메인 전문가가 설계상의 어디에도 표현돼 있지 않은 어휘를 사용하는가?

• 새로운 단어는 유용한 개념을 찾기 위한 대화와 지식 탐구로 이어진다

• 누락된 용어(개념)를 설계에 포함시켜 모델과 설계를 향상시키는 기회가 될수 있다

12년 10월 29일 월요일

Page 5: Domain driven design ch9

어색한 부분을 조사하라

• 필요한 개념이 늘 대화나 문서로 인식할 수 있을 만큼 드러나 있는 것은 아니다

• 설계에서 어색한 부분

• 설명하기 힘들 만큼 복잡한 작업을 수행하는 프로시저

• 관련된 부분이나 새로운 요구사항 탓에 복잡성이 증가하는 부분

• 해결책은 도메인 전문가가 필요한 개념을 발견할 수 있게 돕는 것

• 바람직한 경우: 도메인 전문가가 다양한 아이디어를 고안해서 모델에 시도

• 그 밖의 경우: 개발자가 아이디어를 제안하고, 도메인 전문가의 동의를 구함

12년 10월 29일 월요일

Page 6: Domain driven design ch9

모순점에 대해 깊이 고민하라

• 요구사항 분석시 마주치게 되는 모순은 심층적 모델에 이르는 중요한 단서가 된다

• 모순의 발생 원인

• 용어를 다르게 사용하는 경우

• 도메인을 잘못 이해했을 경우

• 간혹, 두 도메인 전문가가 서로 모순되는 진술을 하는 경우

• 모순에 대한 저자의 견해

• 흥미롭지 않을뿐더러 그렇게 심오한 내용을 암시하지도 않다

• 모든 모순을 해소하는 것은 현실적이지도, 바람직하지도 않다

• 하지만, 심사숙고 하는 과정에서 숨겨진 있던 사실들을 밝히는 계기가 마련될 수 있다

12년 10월 29일 월요일

Page 7: Domain driven design ch9

서적을 참고하라

• 모델의 개념을 조사할 때는 분명해 보이는 사실이라고 해서 간과해서는 안된다

• 서적을 참고할 경우

• 다양한 분야에 대한 근본 개념과 일반적인 통념을 얻을 수 있다

• 이를 통해, 일관성 있고 사려 깊은 관점에서 작업을 시작할 수 있다

12년 10월 29일 월요일

Page 8: Domain driven design ch9

시도하고 또 시도하라

• 모델에서 유용해 보이는 지식을 발견하기까지는 많은 시행착오 과정이 필요

• 모델러/설계자는 자신의 아이디어에 집착해서는 안된다

• 리팩토링을 통해 수정해야 할 것으로 판명된 곳을 지체하지 않고 수정할 수 있게 모델의 상태를 유지

• 시도하고 또 시도하라

• 실험은 유용한 것과 유용하지 않은 것이 무엇인지 배우는 방법

• 설계 과정에서 실수를 피하려고 발버둥 치면, 더 적은 경험을 바탕으로 해야하는 탓에 품질이 더 낮은 결과물을 얻게 될 것이다

12년 10월 29일 월요일

Page 9: Domain driven design ch9

다소 불명확한 개념을 모델링하는 법

• 객체지향 설계 입문자가 명확한 개념으로 인식하기 힘든 세가지 범주

• 명시적인 제약조건

• 도메인 객체로서의 프로세스

• SPECIFICATION

12년 10월 29일 월요일

Page 10: Domain driven design ch9

명시적인 제약 조건(1/2)

• 암시적인 상태의 제약조건을, 명시적으로 표현할 경우 설계를 대폭 개선

• ex) “Bucket” 객체는 제한된 용량을 초과해서 저장할 수 있다는 제약조건

[ Team LiB ]

How to Model Less Obvious Kinds of ConceptsThe object-oriented paradigm leads us to look for and invent certain kinds of concepts. Things,even very abstract ones such as "accruals," are the meat of most object models, along with theactions those things take. These are the "nouns and verbs" that introductory object-orienteddesign books talk about. But other important categories of concepts can be made explicit in amodel as well.

I'll discuss three such categories that were not obvious to me when I started with objects. Mydesigns became sharper with each one of these I learned.

Explicit Constraints

Constraints make up a particularly important category of model concepts. They often emergeimplicitly, and expressing them explicitly can greatly improve a design.

Sometimes constraints find a natural home in an object or method. A "Bucket" object mustguarantee the invariant that it does not hold more than its capacity.

Figure 9.10.

A simple invariant like this can be enforced using case logic in each operation capable of changingcontents.

class Bucket { private float capacity; private float contents;

public void pourIn(float addedVolume) { if (contents + addedVolume > capacity) { contents = capacity; } else { contents = contents + addedVolume;

class Bucket {private float capacity; private float contents;public void pourIn(float addedVolume) {

if (contents + addedVolume > capacity) { contents = capacity;

} else {contents = contents + addedVolume;

}}

}

class Bucket {private float capacity;private float contents;public void pourIn(float addedVolume) {

float volumePresent = contents + addedVolume;contents = constrainedToCapacity(volumePresent);

}private float constrainedToCapacity(float volumePlacedIn) {

if (volumePlacedIn > capacity) return capacity;

return volumePlacedIn;}

}

제약조건을 함수로 분리

문제점✓ 한 메서드 안에 제약 조건을 다 담을 수 없을 경우✓ 객체의 주된 책임을 수행하는데 필요하지 않은 정보를 해당 메소드에서 필요로 하는 경우

12년 10월 29일 월요일

Page 11: Domain driven design ch9

명시적인 제약 조건(2/2)

• 제약 조건을 포함한 객체의 설계가 어딘가 잘못되어 있음을 나타내는 조짐

• 제약조건을 평가하려면 해당 객체의 정의에 적합하지 않은 데이터가 필요한 경우

• 관련된 규칙이 여러 객체에 걸쳐 나타나며, 동일한 계층 구조에 속하지 않는 객체 간에 중복 또는 상속 관계를 강요하는 경우

• 설계와 요구사항에 관한 다양한 논의는 제약조건에 초첨을 맞춰 이뤄지지만 정작 구현 단계에서 절차적인 코드에 묻혀 명시적으로 표현되지 않는 경우

• 제약조건을 표현하는 새로운 클래스 추가

ExampleReview: Overbooking Policy

In Chapter 1, we worked with a common shipping business practice: booking 10 percent morecargo than the transports could handle. (Experience has taught shipping firms that thisoverbooking compensates for last-minute cancellations, so their ships will sail nearly full.)

This constraint on the association between Voyage and Cargo was made explicit, both in thediagrams and in the code, by adding a new class that represented the constraint.

Figure 9.11. The model refactored to make policy explicit

To review the code and reasoning in the full example, see page 17.

Processes as Domain Objects

Right up front, let's agree that we do not want to make procedures a prominent aspect of ourmodel. Objects are meant to encapsulate the procedures and let us think about their goals orintentions instead.

What I am talking about here are processes that exist in the domain, which we have to representin the model. When these emerge, they tend to make for awkward object designs.

The first example in this chapter described a shipping system that routed cargo. This routingprocess was something with business meaning. A SERVICE is one way of expressing such a processexplicitly, while still encapsulating the extremely complex algorithms.

When there is more than one way to carry out a process, another approach is to make thealgorithm itself, or some key part of it, an object in its own right. The choice between processesbecomes a choice between these objects, each of which represents a different STRATEGY. (Chapter12 will look in more detail at the use of STRATEGIES in the domain.)

The key to distinguishing a process that ought to be made explicit from one that should be hiddenis simple: Is this something the domain experts talk about, or is it just part of the mechanism ofthe computer program?

Constraints and processes are two broad categories of model concepts that don't come leaping tomind when programming in an object-oriented language, yet they can really sharpen up a designonce we start thinking about them as model elements.

Some useful categories of concepts are much narrower. I'll round out this chapter with one muchmore specific, yet quite common. SPECIFICATION provides a concise way of expressing certain kindsof rules, extricating them from conditional logic and making them explicit in the model.

12년 10월 29일 월요일

Page 12: Domain driven design ch9

도메인 객체로서의 프로세스

• 객체는 절차를 캡슐화해서 절차 대신 객체의 목표나 의도에 관해 생각하게 해야 한다

• 모델내에 프로세스가 나타나면 객체를 어색하게 설계하는 경향이 있음

• 프로세스를 도메인 객체로 표현하는 방법

• SERVICE는 프로세스를 명시적으로 표현

• 프로세스를 수행하는 방법이 한가지 이상일 경우

• 프로세스를 STRATEGY로 표현

• 명시적으로 표현해야할 프로세스와 숨겨야할 프로세스의 구분법

• 도메인 전문가가 이야기 하고 있는 프로세스인가? => 명시적으로 표현

12년 10월 29일 월요일

Page 13: Domain driven design ch9

SPECIFICATION

• 모든 종류의 애플리케이션에는 규칙을 검사하는 Boolean 테스트 메서드가 존재

• ex) Invoice의 지불 기간이 지났는지를 검사하는 함수

• 고객의 계정 상태에 따른 지불 유예기간 정책과 같은 복잡한 규칙이 추가 된다면?

• 지불요청을 의미하는 Invoice의 명료함이 규칙에 묻혀 사라질 것

• 여러 도메인 클래스 및 하위 시스템에 의존성을 가지게 될 것

• 그렇다면 어떻게 할 것인가?

• 응용 계층으로 분리?

public boolean isOverdue() {Date currentDate = new Date(); return currentDate.after(dueDate);

}

12년 10월 29일 월요일

Page 14: Domain driven design ch9

SPECIFICATION

• 명세를 위한 객체

• 다른 객체가 SPECIFICATION에 명시된 기준을 만족하는지 검사

• 특별한 목적을 위한 명시적인 VALUE OBJECT

• 규칙을 도메인 계층에 유지

• SPECIFICATION의 적용

• 업무 규칙이 ENTITY나 VALUE OBJECT가 맡고 있는 책임에 맞지 않는 경우

• 규칙의 다양성과 조합이 도메인 객체의 기본의미를 압도하는 경우

• 규칙을 도메인 계층으로 분리한다면 도메인 코드가 더는 모델을 표현할 수 없음

I developed SPECIFICATION in collaboration with Martin Fowler (Evans and Fowler 1997). Thesimplicity of the concept belies the subtlety in application and implementation, so there is a lot ofdetail in this section. There will be even more discussion in Chapter 10, where the pattern isextended. After reading the initial explanation of the pattern that follows, you may want to skimthe "Applying and Implementing SPECIFICATIONS" section, until you are actually attempting to applythe pattern.

Specification

In all kinds of applications, Boolean test methods appear that are really parts of little rules. As longas they are simple, we handle them with testing methods, such as anIterator.hasNext() oranInvoice.isOverdue(). In an Invoice class, the code in isOverdue() is an algorithm thatevaluates a rule. For example,

public boolean isOverdue() { Date currentDate = new Date(); return currentDate.after(dueDate);}

But not all rules are so simple. On the same Invoice class, another rule,anInvoice.isDelinquent() would presumably start with testing if the Invoice is overdue, butthat would just be the beginning. A policy on grace periods could depend on the status of thecustomer's account. Some delinquent invoices will be ready for a second notice, while others willbe ready to be sent to a collection agency. The payment history of the customer, company policyon different product lines . . . the clarity of Invoice as a request for payment will soon be lost inthe sheer mass of rule evaluation code. The Invoice will also develop all sorts of dependencies ondomain classes and subsystems that do not support that basic meaning.

At this point, in an attempt to save the Invoice class, a developer will often refractor the ruleevaluation code into the application layer (in this case, a bill collection application). Now the ruleshave been separated from the domain layer altogether, leaving behind a dead data object thatdoes not express the rules inherent in the business model. These rules need to stay in the domainlayer, but they don't fit into the object being evaluated (the Invoice in this case). Not only that,but evaluating methods swell with conditional code, which make the rule hard to read.

Developers working in the logic-programming paradigm would handle this situation differently.Such rules would be expressed as predicates. Predicates are functions that evaluate to "true" or"false" and can be combined using operators such as "AND" and "OR" to express more complexrules. With predicates, we could declare rules explicitly and use them with the Invoice. If only wewere in the logic paradigm.

Seeing this, people have made attempts at implementing logical rules in terms of objects. Somesuch attempts were very sophisticated, others naive. Some were ambitious, others modest. Someturned out valuable, some were tossed aside as failed experiments. A few attempts were allowedto derail their projects. One thing is clear: As appealing as the idea is, full implementation of logicin objects is a major undertaking. (After all, logic programming is a whole modeling and designparadigm in its own right.)

Business rules often do not fit the responsibility of any of the obvious ENTITIES or VALUE

12년 10월 29일 월요일

Page 15: Domain driven design ch9

SPECIFICATION 용도

• 3가지 용도는 개념적 차원에서는 동일함.

• 검증: 객체가 어떤 요건을 충족시키거나 특정 목적으로 사용할 수 있는지 검사

• 선택: 컬렉션 내의 객체를 선택

• 요청 구축: 특정한 요구 사항을 만족하는 새로운 객체의 생성을 명시

12년 10월 29일 월요일

Page 16: Domain driven design ch9

SPECIFICATION - 검증

• SPECIFICATION의 개념을 가장 직관적으로 사용하는 방식

• 특정한조건에 부합하는지 여부를 판단하여, 결과를 기반으로 특정 행위를 수행

3.

possibly contradictory forms. The conceptual unity can be lost. Applying the SPECIFICATION patternallows a consistent model to be used, even when the implementation may have to diverge.

Validation

The simplest use of a SPECIFICATION is validation, and it is the use that demonstrates the conceptmost straightforwardly.

Figure 9.14. A model applying a SPECIFICATION for validation

class DelinquentInvoiceSpecification extends InvoiceSpecification { private Date currentDate; // An instance is used and discarded on a single date

public DelinquentInvoiceSpecification(Date currentDate) { this.currentDate = currentDate;}

public boolean isSatisfiedBy(Invoice candidate) { int gracePeriod = candidate.customer().getPaymentGracePeriod(); Date firmDeadline = DateUtility.addDaysToDate(candidate.dueDate(), gracePeriod); return currentDate.after(firmDeadline); }

}

Now, suppose we need to display a red flag whenever a salesperson brings up a customer withdelinquent bills. We just have to write a method in a client class, something like this.

public boolean accountIsDelinquent(Customer customer) { Date today = new Date(); Specification delinquentSpec = new DelinquentInvoiceSpecification(today); Iterator it = customer.getInvoices().iterator(); while (it.hasNext()) { Invoice candidate = (Invoice) it.next();

class DelinquentInvoiceSpecification extends InvoiceSpecification {private Date currentDate;// An instance is used and discarded on a single datepublic DelinquentInvoiceSpecification(Date currentDate) {

this.currentDate = currentDate;}public boolean isSatisfiedBy(Invoice candidate) {

int gracePeriod = candidate.customer().getPaymentGracePeriod();Date firmDeadline = DateUtility.addDaysToDate(candidate.dueDate(), gracePeriod);return currentDate.after(firmDeadline);

}}

public boolean accountIsDelinquent(Customer customer) { Date today = new Date();Specification delinquentSpec = new DelinquentInvoiceSpecification(today); Iterator it = customer.getInvoices().iterator(); while (it.hasNext()) {

Invoice candidate = (Invoice) it.next();if (delinquentSpec.isSatisfiedBy(candidate))

return true; }return false;

}

12년 10월 29일 월요일

Page 17: Domain driven design ch9

SPECIFICATION - 선택 (1/3)

• 특정한 조건을 기반으로 객체 컬렉션의 일부를 선택

• SPECIFICATION을 유지하며 관계형 데이터 베이스에 질의

• 맵핑 프레임워크를 사용하지 않는 경우

public Set selectSatisfying(InvoiceSpecification spec) {Set results = new HashSet(); Iterator it = invoices.iterator(); while (it.hasNext()) {

Invoice candidate = (Invoice) it.next();if (spec.isSatisfiedBy(candidate)) results.add(candidate);

}return results;

}

"SELECT * FROM INVOICE, CUSTOMER" + " WHERE INVOICE.CUST_ID = CUSTOMER.ID" + " AND INVOICE.DUE_DATE + CUSTOMER.GRACE_PERIOD" + " < " + SQLUtility.dateAsSQL(currentDate);}

SPECIFICATIONS mesh smoothly with REPOSITORIES, which are the building-block mechanisms forproviding query access to domain objects and encapsulating the interface to the database (seeFigure 9.15).

Figure 9.15. The interaction between REPOSITORY and SPECIFICATION

Now this design has some problems. Most important, the details of the table structure have leakedinto the DOMAIN LAYER; they should be isolated in a mapping layer that relates the domain objectsto the relational tables. Implicitly duplicating that information here could hurt the modifiability andmaintainability of the Invoice and Customer objects, because any change to their mappings nowhave to be tracked in more than one place. But this example is a simple illustration of how to keepthe rule in just one place. Some object-relational mapping frameworks provide the means toexpress such a query in terms of the model objects and attributes, generating the actual SQL inthe infrastructure layer. This would let us have our cake and eat it too.

When the infrastructure doesn't come to the rescue, we can refactor the SQL out of the expressivedomain objects by adding a specialized query method to the Invoice Repository. To avoidembedding the rule into the REPOSITORY, we have to express the query in a more generic way, onethat doesn't capture the rule but can be combined or placed in context to work the rule out (in thisexample, by using a double dispatch).

public class InvoiceRepository {

public Set selectWhereGracePeriodPast(Date aDate){ //This is not a rule, just a specialized query String sql = whereGracePeriodPast_SQL(aDate); ResultSet queryResultSet =

public String asSQL() { return "SELECT * FROM INVOICE, CUSTOMER" +

" WHERE INVOICE.CUST_ID = CUSTOMER.ID" +" AND INVOICE.DUE_DATE + CUSTOMER.GRACE_PERIOD" + " < " + SQLUtility.dateAsSQL(currentDate);

}

문제점✓ 테이블 구조가 Domain Layer에 노출

12년 10월 29일 월요일

Page 18: Domain driven design ch9

SPECIFICATION - 선택 (2/3)

• Double Dispatch 적용public class InvoiceRepository {

public Set selectWhereGracePeriodPast(Date aDate){ //This is not a rule, just a specialized query String sql = whereGracePeriodPast_SQL(aDate); ResultSet queryResultSet = SQLDatabaseInterface.instance().executeQuery(sql);

return buildInvoicesFromResultSet(queryResultSet);}public String whereGracePeriodPast_SQL(Date aDate) {

return "SELECT * FROM INVOICE, CUSTOMER" +" WHERE INVOICE.CUST_ID = CUSTOMER.ID" +" AND INVOICE.DUE_DATE + CUSTOMER.GRACE_PERIOD" + " < " + SQLUtility.dateAsSQL(aDate);

}public Set selectSatisfying(InvoiceSpecification spec) {

return spec.satisfyingElementsFrom(this);}

}

public class DelinquentInvoiceSpecification {// Basic DelinquentInvoiceSpecification code herepublic Set satisfyingElementsFrom(InvoiceRepository repository) {

//Delinquency rule is defined as:// "grace period past as of current date"return repository.selectWhereGracePeriodPast(currentDate);

}}

문제점✓체납 개념을 구성하는 본질적인 규칙은 SPECIFICATION에 명시 되었으나, 규칙을 깔끔하게 모으지 못함

12년 10월 29일 월요일

Page 19: Domain driven design ch9

SPECIFICATION - 선택 (3/3)

• REPOSITORY를 일반화 상태로 유지public class InvoiceRepository {

public Set selectWhereDueDateIsBefore(Date aDate) { String sql = whereDueDateIsBefore_SQL(aDate); ResultSet queryResultSet = SQLDatabaseInterface.instance().executeQuery(sql);return buildInvoicesFromResultSet(queryResultSet);

}public String whereDueDateIsBefore_SQL(Date aDate) {

return "SELECT * FROM INVOICE" +" WHERE INVOICE.DUE_DATE" +" < " + SQLUtility.dateAsSQL(aDate);

}public Set selectSatisfying(InvoiceSpecification spec) {

return spec.satisfyingElementsFrom(this);}

}

public class DelinquentInvoiceSpecification { //Basic DelinquentInvoiceSpecification code herepublic Set satisfyingElementsFrom(InvoiceRepository repository) {

Collection pastDueInvoices = repository.selectWhereDueDateIsBefore(currentDate);Set delinquentInvoices = new HashSet(); Iterator it = pastDueInvoices.iterator(); while (it.hasNext()) {

Invoice anInvoice = (Invoice) it.next(); if (this.isSatisfiedBy(anInvoice))

delinquentInvoices.add(anInvoice);}

return delinquentInvoices;}

}

문제점✓성능 저하 발생✓성능 vs. 책임분할에 대한 검증 필요

12년 10월 29일 월요일

Page 20: Domain driven design ch9

SPECIFICATION - 요청 구축

• SPECIFICATION을 만족하는 객체를 생성

• SPECIFICATION을 사용하지 않고 Generator을 사용할 경우

• Generator 내에 행위를 암시적으로 규정해야 함

• SPECIFICATION을 사용할 경우

• Generator의 구현을 인터페이스로부터 분리 (암시적인 규정)

• 개발자가 세부사항을 이해하지 않고도 결과물을 예측 가능

• 생성 요청을 하는 코드는 클라이언트에 있기 때문에 더 유연한 개선이 가능

• SPECIFICATION을 통해 생성된 결과물을 검증 가능

12년 10월 29일 월요일

Page 21: Domain driven design ch9

요약

• 도메인의 숨겨진 개념을 명확하게 인식하는 방법

• 언어에 귀를 기울여라

• 어색한 부분을 조사하라

• 모순점에 대해 깊이 고민하라

• 시도하고 또 시도하라

• 객체지향 설계 입문자가 명확한 개념으로 인식하기 힘든 세가지 범주

• 명시적인 제약조건

• 도메인 객체로서의 프로세스

• SPECIFICATION

12년 10월 29일 월요일