Domain driven design ch9

  • 382 views
Uploaded on

 

More in: Technology
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
    Be the first to like this
No Downloads

Views

Total Views
382
On Slideshare
0
From Embeds
0
Number of Embeds
0

Actions

Shares
Downloads
3
Comments
0
Likes
0

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Ch.9 암시적인 개념을 명확하게 (Domain Driven Design) chois7912년 10월 29일 월요일
  • 2. About This Chapter • 심층 모델은 도메인에 대한 본질적인 지식을 간결하고 유연하게 표현하는 중심 개념과 추상화를 담고 있는 중요한 요소이다 • 심층 모델 생성 과정 • 도메인의 본질적인 개념을 모델로 표현 • 성공적인 지식 탐구와 리팩토링을 반복하며 정제 • 중요한 개념이 모델과 설계에 명확하게 인식되고 표현이 되었을 때 가능 • 그렇다면, 어떻게 중요한 개념을 모델과 설계 내에 명확히 표현되게 할것인가? • 전통적인 방식: 요구사항 문서에 서술된 명사와 동사를 식별 • 명사 -> 객체 이름, 동사 -> 객체의 메서드 • 얄팍한 지식에 기반을 둔 투박하고 무의미한 모델인 경우가 대부분 • 도메인 내의 암시적인 개념을 명확히 인식하고 반영하는 것이 필요12년 10월 29일 월요일
  • 3. 개념 파헤치기 • 개발자는 잠재해 있는 암시적인 개념을 드러내는 단서에 민감해야 한다 • 언어에 귀를 기울여라 • 어색한 부분을 조사하라 • 모순점에 대해 깊이 고민하라 • 시도하고 또 시도하라12년 10월 29일 월요일
  • 4. 언어에 귀를 기울여라 • 도메인 전문가가 사용하는 언어에 귀 기울여라 • 복잡하게 뒤얽힌 개념들을 간결하게 표현하는 용어가 있는가? • 선택한 단어를 더 적절하게 고쳐주는가? • 특정 문구를 이야기할 때 도메인 전문가의 얼굴에서 곤혹스러운 표정이 사라지는가? • 개발자와 도메인 전문가가 설계상의 어디에도 표현돼 있지 않은 어휘를 사용하는가? • 새로운 단어는 유용한 개념을 찾기 위한 대화와 지식 탐구로 이어진다 • 누락된 용어(개념)를 설계에 포함시켜 모델과 설계를 향상시키는 기회가 될수 있다12년 10월 29일 월요일
  • 5. 어색한 부분을 조사하라 • 필요한 개념이 늘 대화나 문서로 인식할 수 있을 만큼 드러나 있는 것은 아니다 • 설계에서 어색한 부분 • 설명하기 힘들 만큼 복잡한 작업을 수행하는 프로시저 • 관련된 부분이나 새로운 요구사항 탓에 복잡성이 증가하는 부분 • 해결책은 도메인 전문가가 필요한 개념을 발견할 수 있게 돕는 것 • 바람직한 경우: 도메인 전문가가 다양한 아이디어를 고안해서 모델에 시도 • 그 밖의 경우: 개발자가 아이디어를 제안하고, 도메인 전문가의 동의를 구함12년 10월 29일 월요일
  • 6. 모순점에 대해 깊이 고민하라 • 요구사항 분석시 마주치게 되는 모순은 심층적 모델에 이르는 중요한 단서가 된다 • 모순의 발생 원인 • 용어를 다르게 사용하는 경우 • 도메인을 잘못 이해했을 경우 • 간혹, 두 도메인 전문가가 서로 모순되는 진술을 하는 경우 • 모순에 대한 저자의 견해 • 흥미롭지 않을뿐더러 그렇게 심오한 내용을 암시하지도 않다 • 모든 모순을 해소하는 것은 현실적이지도, 바람직하지도 않다 • 하지만, 심사숙고 하는 과정에서 숨겨진 있던 사실들을 밝히는 계기가 마련될 수 있다12년 10월 29일 월요일
  • 7. 서적을 참고하라 • 모델의 개념을 조사할 때는 분명해 보이는 사실이라고 해서 간과해서는 안된다 • 서적을 참고할 경우 • 다양한 분야에 대한 근본 개념과 일반적인 통념을 얻을 수 있다 • 이를 통해, 일관성 있고 사려 깊은 관점에서 작업을 시작할 수 있다12년 10월 29일 월요일
  • 8. 시도하고 또 시도하라 • 모델에서 유용해 보이는 지식을 발견하기까지는 많은 시행착오 과정이 필요 • 모델러/설계자는 자신의 아이디어에 집착해서는 안된다 • 리팩토링을 통해 수정해야 할 것으로 판명된 곳을 지체하지 않고 수정할 수 있게 모델의 상태를 유지 • 시도하고 또 시도하라 • 실험은 유용한 것과 유용하지 않은 것이 무엇인지 배우는 방법 • 설계 과정에서 실수를 피하려고 발버둥 치면, 더 적은 경험을 바탕으로 해야하는 탓에 품질이 더 낮은 결과물을 얻게 될 것이다12년 10월 29일 월요일
  • 9. 다소 불명확한 개념을 모델링하는 법 • 객체지향 설계 입문자가 명확한 개념으로 인식하기 힘든 세가지 범주 • 명시적인 제약조건 • 도메인 객체로서의 프로세스 • SPECIFICATION12년 10월 29일 월요일
  • 10. How to Model Less Obvious Kinds of Concepts The 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 the actions those things take. These are the "nouns and verbs" that introductory object-oriented design books talk about. But other important categories of concepts can be made explicit in a model as well. 명시적인 제약 조건(1/2) Ill discuss three such categories that were not obvious to me when I started with objects. My designs became sharper with each one of these I learned. Explicit Constraints Constraints make up a particularly important category of model concepts. They often emerge • 암시적인 상태의 제약조건을, 명시적으로 표현할 경우 설계를 대폭 개선 implicitly, and expressing them explicitly can greatly improve a design. Sometimes constraints find a natural home in an object or method. A "Bucket" object must guarantee the invariant that it does not hold more than its capacity. • ex) “Bucket” 객체는 제한된 용량을 초과해서 저장할 수 있다는 제약조건 Figure 9.10. class Bucket { private float capacity; private float contents; public void pourIn(float addedVolume) { if (contents + addedVolume > capacity) { contents = capacity; } else { contents = contents + addedVolume; } } } A simple invariant like this can be enforced using case logic in each operation capable of changing contents. 제약조건을 함수로 분리 class Bucket { class Bucket { privateprivate float capacity; float capacity; privateprivate float contents; float contents; public public void pourIn(float addedVolume) { void pourIn(float addedVolume) { float volumePresent = contents + addedVolume; if (contents + addedVolume > capacity) { contents = constrainedToCapacity(volumePresent); contents = capacity; 문제점 } ✓ 한 메서드 안에 제약 조건을 다 담을 수 없을 경우 } else { contents =float constrainedToCapacity(float volumePlacedIn) { private contents + addedVolume; if (volumePlacedIn > capacity) ✓ 객체의 주된 책임을 수행하는데 필요하지 않은 정보를 return capacity; 해당 메소드에서 필요로 하는 경우 return volumePlacedIn; } }12년 10월 29일 월요일
  • 11. 명시적인 제약 조건(2/2) • 제약 조건을 포함한 객체의 설계가 어딘가 잘못되어 있음을 나타내는 조짐 • 제약조건을 평가하려면 해당 객체의 정의에 적합하지 않은 데이터가 필요한 경우 Example Review: Overbooking Policy • 관련된 규칙이 여러 객체에 걸쳐 나타나며, 동일한 계층 구조에 속하지 않는 객체 간에 중복 또는 상속 관계를 강요하는 경우 In Chapter 1, we worked with a common shipping business practice: booking 10 percent more cargo than the transports could handle. (Experience has taught shipping firms that this overbooking 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 the 명시적으로 표현되지 않는 경우 diagrams 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 Objects12년 10월 29일 월요일
  • 12. 도메인 객체로서의 프로세스 • 객체는 절차를 캡슐화해서 절차 대신 객체의 목표나 의도에 관해 생각하게 해야 한다 • 모델내에 프로세스가 나타나면 객체를 어색하게 설계하는 경향이 있음 • 프로세스를 도메인 객체로 표현하는 방법 • SERVICE는 프로세스를 명시적으로 표현 • 프로세스를 수행하는 방법이 한가지 이상일 경우 • 프로세스를 STRATEGY로 표현 • 명시적으로 표현해야할 프로세스와 숨겨야할 프로세스의 구분법 • 도메인 전문가가 이야기 하고 있는 프로세스인가? => 명시적으로 표현12년 10월 29일 월요일
  • 13. SPECIFICATION • 모든 종류의 애플리케이션에는 규칙을 검사하는 Boolean 테스트 메서드가 존재 • ex) Invoice의 지불 기간이 지났는지를 검사하는 함수 public boolean isOverdue() { Date currentDate = new Date(); return currentDate.after(dueDate); } • 고객의 계정 상태에 따른 지불 유예기간 정책과 같은 복잡한 규칙이 추가 된다면? • 지불요청을 의미하는 Invoice의 명료함이 규칙에 묻혀 사라질 것 • 여러 도메인 클래스 및 하위 시스템에 의존성을 가지게 될 것 • 그렇다면 어떻게 할 것인가? • 응용 계층으로 분리?12년 10월 29일 월요일
  • 14. SPECIFICATION • 명세를 위한 객체 • 다른 객체가 SPECIFICATION에 명시된 기준을 만족하는지 검사 I developed SPECIFICATION in collaboration with Martin Fowler (Evans and Fowler 1997). The simplicity of the concept belies the subtlety in application and implementation, so there is a lot of detail in this section. There will be even more discussion in Chapter 10, where the pattern is • 특별한 목적을 위한 명시적인 VALUE OBJECT extended. After reading the initial explanation of the pattern that follows, you may want to skim the "Applying and Implementing SPECIFICATIONS" section, until you are actually attempting to apply the pattern. • 규칙을 도메인 계층에 유지 Specification In all kinds of applications, Boolean test methods appear that are really parts of little rules. As long as they are simple, we handle them with testing methods, such as anIterator.hasNext() or anInvoice.isOverdue(). In an Invoice class, the code in isOverdue() is an algorithm that evaluates a rule. For example, • SPECIFICATION의 적용 public boolean isOverdue() { Date currentDate = new Date(); return currentDate.after(dueDate); • 업무 규칙이 ENTITY나 VALUE OBJECT가 맡고 있는 책임에 맞지 않는 경우 } 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, but • 규칙의 다양성과 조합이 도메인 객체의 기본의미를 압도하는 경우 that would just be the beginning. A policy on grace periods could depend on the status of the customers account. Some delinquent invoices will be ready for a second notice, while others will be ready to be sent to a collection agency. The payment history of the customer, company policy on different product lines . . . the clarity of Invoice as a request for payment will soon be lost in • 규칙을 도메인 계층으로 분리한다면 도메인 코드가 더는 모델을 표현할 수 없음 the sheer mass of rule evaluation code. The Invoice will also develop all sorts of dependencies on domain 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 rule evaluation code into the application layer (in this case, a bill collection application). Now the rules have been separated from the domain layer altogether, leaving behind a dead data object that does not express the rules inherent in the business model. These rules need to stay in the domain layer, but they dont 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.12년 10월 29일 월요일
  • 15. SPECIFICATION 용도 • 3가지 용도는 개념적 차원에서는 동일함. • 검증: 객체가 어떤 요건을 충족시키거나 특정 목적으로 사용할 수 있는지 검사 • 선택: 컬렉션 내의 객체를 선택 • 요청 구축: 특정한 요구 사항을 만족하는 새로운 객체의 생성을 명시12년 10월 29일 월요일
  • 16. 3. SPECIFICATION - 검증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 • SPECIFICATION의 개념을 가장 직관적으로 사용하는 방식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 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; }class DelinquentInvoiceSpecification extends return false; InvoiceSpecification { } private Date currentDate; // An instance is used and discarded on a single date class DelinquentInvoiceSpecification extends InvoiceSpecification { private Date currentDate; public DelinquentInvoiceSpecification(Date currentDate) { // An instance is used and discarded on a single date this.currentDate = currentDate;} public DelinquentInvoiceSpecification(Date currentDate) { this.currentDate = currentDate; public boolean isSatisfiedBy(Invoice candidate) { } int gracePeriodboolean isSatisfiedBy(Invoice candidate) { public = int gracePeriod = candidate.customer().getPaymentGracePeriod(); candidate.customer().getPaymentGracePeriod(); Date firmDeadline firmDeadline = DateUtility.addDaysToDate(candidate.dueDate(), gracePeriod); Date = return currentDate.after(firmDeadline); DateUtility.addDaysToDate(candidate.dueDate(), } gracePeriod); } return currentDate.after(firmDeadline); }} 12년 10월 29일 월요일
  • 17. SPECIFICATION - 선택 (1/3) "SELECT * FROM INVOICE, CUSTOMER" + " WHERE INVOICE.CUST_ID = CUSTOMER.ID" + " AND INVOICE.DUE_DATE + CUSTOMER.GRACE_PERIOD" + • 특정한 조건을 기반으로 객체 컬렉션의 일부를 선택 " < " + SQLUtility.dateAsSQL(currentDate); } public Set selectSatisfying(InvoiceSpecification spec) { Set results = new HashSet(); Iterator it = invoices.iterator(); SPECIFICATIONS mesh smoothly with REPOSITORIES, which are the building-block mechanisms while (it.hasNext()) { providing query access to domain objects and encapsulating the interface to the database (s Invoice candidate = (Invoice) it.next(); Figure 9.15). if (spec.isSatisfiedBy(candidate)) results.add(candidate); } } return results; Figure 9.15. The interaction between REPOSITORY and SPECIFICATIO • SPECIFICATION을 유지하며 관계형 데이터 베이스에 질의 • 맵핑 프레임워크를 사용하지 않는 경우 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에 노출 Now this design has some problems. Most important, the details of the table structure have into the DOMAIN LAYER; they should be isolated in a mapping layer that relates the domain ob12년 10월 29일 월요일
  • 18. 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); ✓체납 개념을 구성하는 본질적인 규칙은 SPECIFICATION에 명시 } 되었으나, 규칙을 깔끔하게 모으지 못함 } public class DelinquentInvoiceSpecification { // Basic DelinquentInvoiceSpecification code here public Set satisfyingElementsFrom(InvoiceRepository repository) { //Delinquency rule is defined as: // "grace period past as of current date" return repository.selectWhereGracePeriodPast(currentDate); } }12년 10월 29일 월요일
  • 19. 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); ✓성능 저하 발생 } ✓성능 vs. 책임분할에 대한 검증 필요 } public class DelinquentInvoiceSpecification { //Basic DelinquentInvoiceSpecification code here public 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; } }12년 10월 29일 월요일
  • 20. SPECIFICATION - 요청 구축 • SPECIFICATION을 만족하는 객체를 생성 • SPECIFICATION을 사용하지 않고 Generator을 사용할 경우 • Generator 내에 행위를 암시적으로 규정해야 함 • SPECIFICATION을 사용할 경우 • Generator의 구현을 인터페이스로부터 분리 (암시적인 규정) • 개발자가 세부사항을 이해하지 않고도 결과물을 예측 가능 • 생성 요청을 하는 코드는 클라이언트에 있기 때문에 더 유연한 개선이 가능 • SPECIFICATION을 통해 생성된 결과물을 검증 가능12년 10월 29일 월요일
  • 21. 요약 • 도메인의 숨겨진 개념을 명확하게 인식하는 방법 • 언어에 귀를 기울여라 • 어색한 부분을 조사하라 • 모순점에 대해 깊이 고민하라 • 시도하고 또 시도하라 • 객체지향 설계 입문자가 명확한 개념으로 인식하기 힘든 세가지 범주 • 명시적인 제약조건 • 도메인 객체로서의 프로세스 • SPECIFICATION12년 10월 29일 월요일