• Save
DDD 구현기초 (거의 Final 버전)
Upcoming SlideShare
Loading in...5
×
 

Like this? Share it with your network

Share

DDD 구현기초 (거의 Final 버전)

on

  • 10,434 views

DDD 개요 및 구현에 관한 기초 세미나 자료.

DDD 개요 및 구현에 관한 기초 세미나 자료.

Statistics

Views

Total Views
10,434
Views on SlideShare
4,870
Embed Views
5,564

Actions

Likes
31
Downloads
0
Comments
0

7 Embeds 5,564

http://javacan.tistory.com 5510
http://oriya-tech.tumblr.com 47
http://localhost 2
http://localhost:8080 2
https://twitter.com 1
https://www.google.co.kr 1
http://www.google.co.kr 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

CC Attribution-NonCommercial LicenseCC Attribution-NonCommercial License

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

DDD 구현기초 (거의 Final 버전) Presentation Transcript

  • 1. 짜친 세미나, DDD 쌩기초 이야기- 거의 Final Version최범균 (madvirus@madvirus.net)자바캔 카페(http://cafe.daum.net/javacan)자바캔 블로그(http://javacan.tistory.com)
  • 2. TOC• 객체와 연관 – 객체와 연관• DDD 쌩 기초 – 레이어 구성 – DDD 빌딩 블록• DDD와 ORM• DDD 구현 이야기 – ORM 이용 구현하기 • 도메인 모델 구현 • 리포지토리의 구현 – 어플리케이션 레이어 서비스 구현 – UI 영역(컨트롤러) 구현• 참고 자료 2
  • 3. 객체와 연관 3
  • 4. 객체란? 객체는 기능 제공 객체 객체 객체 객체 객체 객체 다른 객체를 사용 4
  • 5. 클래스: 객체의 정의• 기능을 public 메서드로 정의 – 데이터는 외부에 드러내지 않고 기능 정의만 제공 • getter/setter 메서드의 최소화• 다른 객체를 사용하는 방법 – 필드를 이용해서 다른 객체를 레퍼런스로 참조 • 연관(Association)이라고 표현 – 메서드에서 필요한 시점에 객체를 생성해서 사용 5
  • 6. 객체 간 연관의 예 필드public class Employee { Organization org = new Organization(…); private String name; private Organization organization; Employee e1 = new Employee(…); ... Employee e2 = new Employee(…); public void setOrganization(Organization org) { this.organization = org; org.add(e1); } org.add(e2);} 필드public class Organization { private String name; private List<Employee> employees; ... public void add(Employee emp) { this.employees.add(emp); emp.setOrganization(this); }} 6
  • 7. 연관의 방향성• 방향성의 종류 – 단 방향: 두 객체 중 한 객체만 레퍼런스 가짐 – 양 방향: 두 객체가 서로에 대한 레퍼런스 가짐• 양 방향 예 emp1:Employee org1:Oragnization rule1:Rule organization=org1 employees = [emp1, emp2] rule = rule1 emp2:Employee organization=org1 7
  • 8. 연관의 종류• Many-To-One – 예, 다수의 Employee가 한 Organization과 연관• One-To-Many – 예, 한 Organization이 다수의 Employee와 연관 – 콜렉션으로 구현: Collection, Set, List, Map• One-To-One – 예, 한 Oranization이 한 Rule과 연관• Many-To-Many – 콜렉션 8
  • 9. 모든 객체가 메모리 있다면… (1)• 연관된 객체들을 이용해서 기능을 구현• 예1, Rule의 교체: 연관된 객체 교환으로 구현 Organization org = …; // 어딘가에서 구함 // Organization의 메서드 Rule newRule = new Rule(…); public void changeRule(Rule newRule) { org.changeRule(newRule); this.rule = newRule; }• 예2, 팀 이동: 양방향 연관을 처리 Organization org2 = …; // 어딘가에서 구함 // Employee의 메서드 Employee emp1 = …; // 어딘가에서 구함 public void transferTo(Organization org) { emp1.changeTeam(org2); Organization oldOrg = this.organization; this.organization = org; oldOrg.remove(this); } // Organization의 메서드 public void remove(Employee e) { employees.remove(e); } 9
  • 10. 모든 객체가 메모리 있다면… (2)• 연관된 객체들을 이용해서 기능을 구현• 예3, Rule의 확인: 연관된 객체를 구해 확인 Organization org = …; // 어딘가에서 구함 // Organization의 메서드 Rule rule = org.getRule(); public Rule getRule() { if (rule != null) { return this.rule; rule.check(someData); } } // Rule의 메서드 publiic void check(Data data) { … }• 예4, Rule의 확인: 내부적으로 위임 Organization org2 = …; // 어딘가에서 구함 // Organization의 메서드 org.checkRule(someData); public void checkRule(Data data) { if (rule == null) { return; } rule.check(data); } 10
  • 11. 모든 객체가 메모리 있다면… (3)• 객체를 메모리에 보관/검색 위한 방법 필요 – 객체마다 고유 식별값 필요 • 메모리 상의 레퍼런스 이용은 한계 • 고유 식별 값을 갖는 객체를 Entity라 함 – 객체 보관하고 제공해주는 기능 필요 • DDD에서는 이를 Repository로 추상화 public interface OrganizationRepository { public void save(Organization org); public Organization findById(Integer id); } Organization org = organizationRepository.findById(id); org.checkRule(…); 11
  • 12. 객체의 라이프사이클• 객체 생성 - 사용 - 소멸 – 동일 라이프사이클을 갖는 객체들의 군 존재 emp1:Employee org1:Oragnization rule1:Rule organization=org1 employees = [emp1, emp2] rule = rule1 emp2:Employee organization=org1 org1이 삭제되면 rule1도 삭제 org1이 삭제되도 emp2는 삭제 안 됨 12
  • 13. DDD 쌩 기초 13
  • 14. DDD(Domain Driven Design)• 도메인 모델로부터 디자인하기• 도메인 모델의 중요성 강조 – 도메인 모델을 최대한 설계에 반영 • 예, Ubiquitous Language (용어 최대한 반영) – 모델이 깨지지 않는 방법 제시 (일관성 유지) • 예, Bounded Context, Anticorruption Layer 등• 이를 위한 기본 설계 기법 제시 – 레이어 구성 – 도메인 레이어의 기본 빌딩 블록 14
  • 15. DDD 구현 위한 필수 쌩 기초 발췌: Domain Driven Design (Eric Evans) 15
  • 16. Layered Architecture 사용자에게 정보 출력 User Intreface 사용자의 요청을 해석 하위 레이어에 전달 어플리케이션의 상태를 관리 / Thin Layer Application 비즈니스 로직/객체 상태를 포함하지 않음 실제 비즈니스 처리는 도메인에 요청 도메인에 대한 정보를 포함 Domain 비즈니스 객체의 상태를 포함 비즈니스 로직을 제공 다른 레이어를 위한 지원 라이브러리 Infrastructure 영속성 구현 16
  • 17. 도메인 모델의 기본 구성 요소• Entity• Value• Aggregate• Repository• Service 17
  • 18. 도메인 모델의 기본 구성 요소, Entity• 주요 특징 – 모델을 표현 – 고유의 식별 값을 가짐 – 모델과 관련된 비즈니스 로직을 수행 – 자신만의 라이프 사이클을 가짐• 예, 평가 시스템에서의 Entity – Employee(직원) – Organization(조직) 18
  • 19. 도메인 모델의 기본 구성 요소, Value• 고유의 키 값을 갖지 않음• 데이터를 표현하기 위한 용도로 주로 사용 – 개념적으로 완전한 데이터 집합 <<Entity>> – (주로) Immutable 임 Employee 자신에게 알맞은 로직을 제공 -id : Integer – -name : String – Entity의 속성으로 사용 -address : Address -organization : Organization • 자신의 라이프사이클을 갖지 않음 • 엔티티를 따름 1• 예 – Address(주소) 1 <<Value>> • 데이터: 우편번호, 주소1, 주소2 Address – Money(돈): -zipcode : String • 데이터: 금액, 화폐 단위 -address1 : String -address2 : String • 로직: 더하기, 곱하기 등 19
  • 20. 도메인 모델의 기본 구성 요소, Aggregate• Aggregate – 관련된 객체들의 묶음• Aggregate의 특징 – 데이터 변경시 한 단위로 처리 됨 • 비슷한 또는 거의 유사한 라이프사이클을 갖는 객체들 – 특히, 삭제 시 함께 삭제됨 – Aggregate는 경계를 가짐 • 기본적으로, 한 Aggregate에 속한 객체는 다른 Aggregate에 속하지 않음• Aggregate 필요성 – 많은 도메인 모델을 가능한 간단하고 이해가능한 수 준으로 만들 필요 – 연관의 개수를 줄임으로써 복잡도 감소 20
  • 21. Aggregate의 흔한 예, 주문-고객-상품 order item <<Entity>> OrderLine <<Entity>> <<Value>> <<Aggregate Root>> -product : Product <<Aggregate Root>> Option Order -quantity : int Product -id : Integer -id : Integer -lines : List<OrderLine> <<Value>> -name : String -shippingAddress : Address Price -listPrice : Price -value : int <<Value>> customer ItemDetail -description : String <<Entity>> Customer -id : String -name : String -address : Address 21
  • 22. 도메인 모델의 기본 구성 요소, Aggregate 루트• Aggregate는 루트를 가짐• Aggregate 루트 역할 – Aggregate의 내부 객체들을 관리 – Aggregate 외부에서 접근할 수 있는 유일한 객체 • Aggregate의 내부 Entity/Value는 루트를 통해 접근 – Aggregate에 대한 알맞은 기능을 수행 • 예, 주문 관련 Aggregate 루트인 Order의 기능 – 주문 취소, 배송지 주소 변경, 주문 상품 변경 • 내부 객체들의 연관을 탐색해서 기능 구현 22
  • 23. Entity, Value, Aggregate 도출 예, 도메인 분석• 도메인: 평가 – 직원의 평가 – 평가는 성과 평가와 역량 평가로 구성 – 성과와 역량에 대해 • 본인 평가, 1차 평가, 2차 평가 존재 – 성과 평가 시, • 성과 항목 목록을 입력하고 각 항목에 대한 평가 내용을 입력 – 역량 평가 시, • 미리 정의된 역량 항목에 대한 평가 내용을 입력 23
  • 24. Entity, Value, Aggregate 도출 예, 도메인 분석 - 실제표성과 목적 결과 가중치 본인 평가 1차 평가 2차 평가평가 전산 고도화 위키 도입 30 의견 / A 의견 / A 의견 / B 그룹웨어 구축 보안 강화 DB 보안 강화 60 의견 / B 의견 / B 의견 / B 보안 프로세스 도입 팀역량강화 팀 세미나 5회 10 의견 / B 의견 / B 의견 / A 영어 참여율 80%역량 역량 항목 본인 평가 1차 평가 2차 평가평가 성취 목표 지향 의견 / A 의견 / A 의견 / B 협동 의견 / A 의견 / A 의견 / B 학습 능력 의견 / B 의견 / B 의견 / B 고객 지향 의견 / A 의견 / S 의견 / A 24
  • 25. Entity, Value, Aggregate 도출 예 평가 Aggregate self <<Value>> <<Entity>> PerfEvalSet RaterPerfEval first -self : RaterPerfEval -ratee : Employee -first : RaterPerfEval -rater : Employee -second : RaterPerfEval self -itemEvals : List<PerfItemRaterEval> 1 -done : boolean <<Entity>> 1 <<Aggregate Root>> <<Entity>> PersonnelEval <<Aggregate Root>> <<Value>> -id : Integer Employee PerfItem -employee : Employee 1 PerfItemRaterEval -name : String -goal : String -perfEvalSet : PerfEvalSet 1..* -grade : Grade -result : String -perfItems : List<PerfItem> -comment : String -perfItem : PerfItem +evaluatePerfBySelf() +evaluatePerfByFirst() +evaluatePerfBySecond() 1 self <<Value>> RaterCompeEval 1 CompefEvalSet -ratee : Employee first -self : RaterCompeEval -rater : Employee -first : RaterCompeEval -itemEvals : List<CompeItemRaterEval> -second : RaterCompeEval second -done : boolean CompeItemRaterEval -grade : Grade -comment : String 25
  • 26. Entity, Value, Aggregate 도출 예, 도메인 분석 - 실제표 perfEvalSet: PerfEvalSet self:PerfEv alSet 성 목적 결과 가중치 본인평가 1차 평가 2차 평가 과 ----- ----- 30 의견 / A 의견 / A 의견 / B self PerfItem <<Value>> <<Entity>> PerfEvalSet RaterPerfEval PerfItem first ----- ----- 60 의견 / B 의견 / B 의견 / B -self : RaterPerfEval -ratee : Employee RaterEval -first : RaterPerfEval -rater : Employee -second : RaterPerfEval self -itemEvals : List<PerfItemRaterEval> ----- ----- 10 의견 / B 의견 / B 의견 / A 1 -done : boolean <<Entity>> 1 <<Aggregate Root>> 역 역량 항목 본인평가 1차 평가 PersonnelEval 평가 2차 량 -id : Integer <<Value>> PerfItem ----- 의견 / A 의견 / A : Employee / B -employee 의견 1 -goal : String PerfItemRaterEval -perfEvalSet : PerfEvalSet -grade : Grade CompeItem 의견 / A : List<PerfItem> B 1..* ----- 의견 / A 의견 / -result : String -perfItems -comment : String RaterEval +evaluatePerfBySelf() -perfItem : PerfItem ----- 의견 / B 의견 / B +evaluatePerfByFirst() / B 의견 +evaluatePerfBySecond() 1 ----- 의견 / A 의견 / S 의견 / A <<Value>> self RaterCompeEval 1 CompefEvalSet -ratee : Employee first -self : RaterCompeEval -rater : Employee self:CompeEv -first : RaterCompeEval -itemEvals : List<CompeItemRaterEval> alSet -second : RaterCompeEval -done : boolean compeEvalSet:CompeEvalSet secondPersonnelEval CompeItemRaterEval -grade : Grade -comment : String 26
  • 27. 도메인 기능 구현 예 - 본인 성과 평가 기능• 본인 평가하기 기능 → 평가 Aggregate의 기능 – 본인 평가하기를 구현하려면 • PersonnelEval의 perfEvalSet 필드로 연결된 • PerfEvalSet의 self 필드로 연결된 • RaterPerfEval을 설정 – 피평가자(ratee), 평가자(rater), 각 항목 평가(itemEvals) self <<Entity>> <<Value>> <<Entity>> <<Aggregate Root>> PerfEvalSet RaterPerfEval 1 first PersonnelEval -self : RaterPerfEval -ratee : Employee -id : Integer 1 -first : RaterPerfEval -rater : Employee -employee : Employee -second : RaterPerfEval self -itemEvals : List<PerfItemRaterEval> -perfEvalSet : PerfEvalSet -done : boolean -perfItems : List<PerfItem> +evaluatePerfBySelf() +evaluatePerfByFirst() 1 PerfItem <<Value>> +evaluatePerfBySecond() 1..* -goal : String PerfItemRaterEval -result : String -grade : Grade -comment : String -perfItem : PerfItem 27
  • 28. 도메인 기능 구현 예 - 본인 성과 평가 기능public class PersonnelEval { private PerfEvalSet perfEvalSet; public void evaluatePerfBySelf(List<PerfItemRaterEval> evals, boolean done) { perfEvalSet.setSelf(employee, evals, done); } …} Aggregate Root가 Aggregate의 다른 객체에 위임public class PerfEvalSet { private RaterPerfEval self; public void setSelf(Employee ratee, List<PerfItemRaterEval> evals, boolean done) { if (self == null) { self = new RaterPerfEval(rater, ratee); } self.change(evals, done); } …} 28
  • 29. 도메인 기능 구현 예 - 본인 성과 평가 기능public class RaterPerfEval { private Employee ratee; private Employee rater; private List<PerfItemRaterEval> evals; private boolean done; public RaterPerfEval(Employee ratee, Employee rater) { this.ratee = ratee; this.rater = rater; } public void change(List<PerfItemRaterEval> evals, boolean done) { this.evals.clear(); PerfItemRaterEval은 Value이고 Immutable 임: this.evals.addAll(evals); 데이터 값 변경이 아닌, 기존 데이터 삭제 후 새로 추가 this.done = done; } …} 29
  • 30. 도메인 기능 구현 예 - 본인 성과 평가 완료 여부 • 본인 평가 완료 여부 → 평가 Aggregate의 기능 – 본인 평가 완료 여부를 구현하려면 • PersonnelEval의 perfEvalSet 필드로 연결된 • PerfEvalSet의 self 필드로 연결된 • RaterPerfEval에게 문의 self <<Entity>> <<Value>> <<Entity>> <<Aggregate Root>> PerfEvalSet RaterPerfEval 1 first PersonnelEval -self : RaterPerfEval -ratee : Employee public class PersonnelEval {-id : Integer 1 -first : RaterPerfEval -rater : Employee-employee : Employee -second : RaterPerfEval -itemEvals : List<PerfItemRaterEval> private PerfEvalSet perfEvalSet; self-perfEvalSet : PerfEvalSet -done : boolean-perfItems : List<PerfItem> public boolean isSelfPerfEvaluationDone() {+evaluatePerfBySelf() return perfEvalSet.isSelfDone(); PerfItem+evaluatePerfByFirst() } 1 <<Value>>+evaluatePerfBySecond() -goal : String } 1..* PerfItemRaterEval -result : String -grade : Grade -comment : String -perfItem : PerfItempublic class PerfEvalSet { public isSelfDone() { return self == null ? false : self.isDone(); } } 30
  • 31. 도메인 모델의 기본 구성 요소, Repository• Entity를 보관하는 장소• 기본 규칙: – Aggregate 당 한 개의 Repository 인터페이스 – Aggregate의 CRUD 기본 단위는 루트! • 예외, 성능 상 이유로 Aggregate에 속한 다른 엔티티에 직접 접근할 필요가 있는 경우 별도 DAO 구현 – 예, 리포트 집계 등• Repository의 기본 인터페이스 – save(루트): 한 Aggregate의 저장 – List<루트> findByName(): 특정 조건으로 검색 – delete(루트): 한 Aggregate의 삭제 31
  • 32. Specification을 통한 검색• Specification – Repository에서 Entity를 검색하기 위한 필터 • DDD에서의 Specification 구현의 모습 public interface Specification<T> { public boolean satisfy(T entity); } public class SomeCustomerRepository implements CustomerRepository { public List<Customer> findBySpecification(Specification<Customer> spec) { List<Customer> all = findAll(); List<Customer> result = new ArrayList<Customer>(); for (Customer c : all) { if (spec.satisfy(c)) { 개념적으로는 맞지만, result.add(c); 모든 Entity 로딩 시, } 성능 문제 발생 } return result; } } 32
  • 33. Specification의 조합• Specification은 서로 조합이 될 때 유용함 – 다수의 Specification을 and/or로 연결 도메인에 맞는 검색 조건 표현Specification<Customer> ageGtSpec = new AgeGraterThanSpec(20);Specification<Customer> locationSpec = new LocationEqSpec(Location.SEOUL);Specification<Customer> ageLocSpec = Specs.and(ageGtSpec, locationSpec); 검색 조건 조합List<Customer> custmoers = customerRepository.findBy(ageLocSpec); Composite Pattern (또는 트리 구조) 이용하여 구현 33
  • 34. 도메인 모델의 기본 구성 요소, 도메인 레이어의 Service• 한 Entity나 Aggregate에 속하지 않은 도메인 기 능을 도메인 서비스로 분리 – 예, 계좌 이체 • 계좌 이체는 한 계좌 엔티티 객체가 수행할 수 없는 도메인 기능• 도메인 서비스의 구현 – 다른 도메인 구성 요소 이용해서 기능 구현• 타 레이어의 서비스와 구분 34
  • 35. DDD 쌩기초의 접근법• 도메인 모델 중심 접근 필요• 객체 연관 중심 접근 필요 – 도메인 기능 구현 시, 도메인 객체 간 연관으로 풀기 • SQL을 머리에서 버릴 것 (특히, JOIN!) 35
  • 36. DDD와 ORM 36
  • 37. 메모리는 영원하지 않음• 안정적인 데이터 관리 위해 객체를 물리적인 기 록 장치에 보관할 필요• 객체 모델과 일치하는 물리적 데이터 저장소는 흔치 않음 – OODB는 시장에서 실패 – RDBS는 딱 들어맞지 않음 – 기타 NoSQL 역시 정확하게 OO와 일치하진 않음 37
  • 38. 차이의 예: 객체 모델과 ER의 대표적인 차이 객체 모델 관계 모델연관 방향성 양방향 단방향 레퍼런스(필드) 이용 Foreign Key 이용콜렉션 연관 List, Set, Map 등 콜렉션 사용 Foreign Key 사용 Join/Collection 테이블 사용다형성 인터페이스/상속 사용 없음 38
  • 39. ORM의 필요성• DDD를 즐겁게 하려면 – 객체를 이용한 모델링 뿐만 아니라, – 객체 모델과 데이터 모델 간 좋은 매핑 도구 필요• 추천 프레임워크 – JPA (자바의 ORM 표준 스펙) • 객체와 RDB 사이의 모든 매핑을 거의 소화 – 또한, Spring Data 추천 – DB 연동 코드 작성을 위한 노가다 감소 – Spring Data MongoDB 등을 통한 NoSQL과의 매핑 처리 39
  • 40. DDD 구현 이야기 40
  • 41. 전체 레이어 별 구성 요소 데이터 조회 목적 Repository 접근해서 도메인 객체 구함 도메인 모델을 DTO로 변환해서 제공 데이터 읽기 위한 트랜잭션 범위 지정도메인 기능 실행 요청 트랜잭션 범위 지정 도메인 모델 객체 제공 영속성 구현 제공 41
  • 42. 전체 레이어 별 구현: Spring + JPASpring BeanSpring MVC Spring Bean Spring TXSpring BeanSpring TX JPA Spring Data JPA Spring Bean JPA 42
  • 43. 도메인 레이어 구현 43
  • 44. ORM을 이용한 DB와의 매핑: Entity & Value• Entity 당 한 개의 테이블 매핑• Entity 간 참조 – 대부분 단방향의 Foreign Key로 처리 – 경우에 따라 Join 테이블 이용 구현• Entity 간 참조는 최대한 단방향으로 – 꼭 필요한 경우에만 양방향• Value – 엔티티와 한 테이블에 함께 존재 • Value 콜렉션의 경우 Collection 테이블로 Value 구현 44
  • 45. Entity & Value 테이블 매핑 예• 엔티티 간 연관: Foreign Key@Entity @Entity@Table(name = "EMPLOYEE") @Table(name = "ORGANIZATION")public class Employee { public class Organization { @Id @Column(name = "EMPLOYEE_ID") @Id private String id; @Column(name = "ORGANIZATION_ID") private Integer id; @Column(name = "NAME") ... private String name; } @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "ORGANIZATION_ID") private Organization organization; ...} 45
  • 46. Entity & Value 테이블 매핑 예• Entity와 같은 테이블 사용하는 Value@Entity @Embeddable@Table(name = "EMPLOYEE") public class Address {public class Employee { @Column(name = "ADDR_1") @Id private String address1; @Column(name = "EMPLOYEE_ID") private String id; @Column(name = "ADDR_2") private String address2; @Column(name = "NAME") } private String name; @Embedded private Address address; EMPLOYEE ...} EMPLOYEE_ID NAME ORGANIZATION_ID ADDR1 ADDR2 46
  • 47. Entity & Value 테이블 매핑 예• Value의 List 콜렉션: 콜렉션 테이블 사용@Entity @Embeddable@Table(name = "RATER_PERF_EVAL") public class PerformanceItemRaterEval {public class RaterPerformanceEval { @Column(name = "GRADE_VALUE") private String gradeValue; @Id @Column(name = "RATER_PERF_EVAL_ID") @Column(name = "COMMENT") private Integer id; private String comment; … @ElementCollection } @CollectionTable(name = "RATER_PERF_ITEM_EVAL", joinColumns = @JoinColumn( name = "RATER_PERF_EVAL_ID")) @OrderColumn(name = "LIST_INDEX") private List<PerformanceItemRaterEval> itemEvals; 47
  • 48. Repository의 구현• 인터페이스로 Repository 정의 – 각 ORM 구현체에 알맞게 구현 • 구현 클래스는 Infrastructure 레이어에 위치 public interface PersonnelEvalRepository { public PersonnelEval findByEmployee(Employee emp); PersonnelEval save(PersonnelEval eval); } public class JpaPersonnelEvalRepository implements PersonnelEvalRepository { @PersistenceContext private EntityManager em; public PersonnelEval findByEmployee(Employee emp) { TypedQuery<PersonnelEval> query = em.createQuery( "select p from PersonnelEval p " + "where p.employee = :emp", PersonnelEval.class); query.setParameter("emp", emp); return query.getSingleResult(); } public PersonnelEval save(PersonnelEval eval) { em.persist(eval); } } 48
  • 49. Specification의 실제 구현• Specification으로부터 조건 생성 기능 필요 – CriteriaQuery 생성 or JP QL의 where 절 생성 public class JpaEmployeeRepository implements EmployeeRepository { @PersistenceContext private EntityManager em; public List<Employee> findBySpecification(Specification spec) { TypedQuery<Employee> query = em.createQuery( "select e from Employee e " + "where " + Specs.toJPQL(spec), Employee.class); spec.setParameters(query); return query.getResultList(); } } – Specification을 직접 구현하는 데는 노력 필요 49
  • 50. Spring Data JPA Repository 자동 생성 Spring Data JPA Spcification 지원 50
  • 51. Spring Data JPA 이용 Repository 구현• Repository를 위한 메서드 Convention 정의 – 인터페이스로부터 런타임 시, 구현 객체 자동 생성public interface EmployeeRepository extends Repository<Employee, String> { Employee findById(String userId); Employee findByName(String name); @Query("select e from Employee e left join e.organization o "+ "where e.rateeTypeValue <> NOT_RATEE order by o.name asc, e.name asc") List<Employee> findAllRatees();} 51
  • 52. Spring Data JPA 이용 Repository 구현, Spec 지원• Specification을 위한 기능 제공 – 검색 조건 표현 위한 Specification 인터페이스 제공public class EmployeeSpecs { public static Specification<Employee> rateeType(final RateeType e) { return new Specification<Employee>() { @Override public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder cb) { return cb.equal(root.get(Employee_.rateeTypeValue), e.name()); } }; } 조건을 도메인 용어로 표현 public static Specification<Employee> nameLike(final String name) { return new Specification<Employee>() { @Override public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder cb) { return cb.like(root.get(Employee_.name), "%" + name + "%"); } }; }} 52
  • 53. Spring Data JPA 이용 Repository 구현, Spec 지원• Specification을 위한 기능 제공 – Repository 메서드에 Specificiation 지정 가능public interface EmployeeRepository extends Repository<Employee, String> { List<Employee> findAll(Specification<Employee> spec);}public interface EmployeeRepository extends Repository<Employee, String>, JpaSpecificationExecutor<Employee> { // JpaSpecificationExecutor에 기본 메서드 정의} 53
  • 54. Spring Data JPA 이용 Repository 구현, Spec 지원• Specification과 Repository의 사용 예 – Repository 메서드에 Specificiation 지정 가능 Criteria와 같은 유연함을 제공 영속 레이어에 대한 의존 제거 (QueryDSL은 EntityManager를 필요) Specifications<Employee> spec = Specifications.where( EmployeeSpecs.rateeType(RateeType.NOT_RATEE)).and( EmployeeSpecs.nameLike("김")); List<Employee> findAll = employeeRepository.findAll(spec); 54
  • 55. Lazy & Eager Loading• Entity 간 연관에서 쓰임 고려한 설정 필요 Employee를 가져올 때, 연관된 Organization도 함께 로딩? Eager Loading Employee emp = employeeRepository.findById("madvirus"); Organization org = emp.getOrganization(); org.getName(); 이 때, Organization과 매핑된 테이블에서 데이터 로딩? Lazy Loading 55
  • 56. 애플리케이션 레이어/UI 레이어 구현 56
  • 57. 전체 레이어 별 구성 요소 데이터 조회 목적 Repository 접근해서 도메인 객체 구함 도메인 모델을 DTO로 변환해서 제공 데이터 읽기 위한 트랜잭션 범위 지정도메인 기능 실행 요청 트랜잭션 범위 지정 57
  • 58. 애플리케이션 레이어 구현• 애플리케이션 서비스의 정의 – 사용자에게 기능 제공 위한 인터페이스 정의 • 기능명/기능정의 메서드/입력 파라미터/리턴 결과로 구성• 도메인을 이용한 구현 – Repository에서 Entity를 가져와 – Entity에 기능을 요청 또는 Entity로부터 정보 조회• 트랜잭션 범위 지정 – 트랜잭션 시작/커밋/롤백 제어 58
  • 59. 애플리케이션 레이어 구현 - 서비스 정의 예public interface ReturnEvaluationService { public ReturnResult returnSelfPerfEval(String rateeId, String raterId);} 처리 결과를 표현 기능 실행에 필요한 입력public interface SelfPerformanceEvaluationService { SelfPerEvalResult evaluate(SelfPerEvalRequest request);} 59
  • 60. 애플리케이션 레이어 구현 - 서비스 구현 예public class DomainReturnEvaluationService implements ReturnEvaluationService { private PersonnelEvalRepository personnelEvalRepository; @Transactional 스프링 트랜잭션 기능 사용 @Override public ReturnResult returnSelfPerfEval(String rateeId, String raterId) { PersonnelEval personnelEval = checkValidRaterAndGet(rateeId, raterId); personnelEval.returnSelfPerfEval(); return new ReturnResult(rateeId, personnelEval.getEmployee().getName()); } 도메인 객체 사용 private PersonnelEval checkValidRaterAndGet(String rateeId, String raterId) { PersonnelEval personnelEval = personnelEvalRepository .findByEmployeeId(rateeId); if (!personnelEval.getEmployee().checkFirstRater(raterId)) { throw new RuntimeException("1차 평가자만 반려 가능"); } return personnelEval; } …} 60
  • 61. UI 레이어의 구현• UI 레이어의 역할 두 가지 – 사용자의 기능 실행 요청을 애플리케이션 레이어에 전달하기 • 서비스가 요구하는 데이터를 전달하면 끝 • 서비스의 처리 결과를 뷰에 보여주면 끝 – 사용자에게 데이터 보여주기 • 도메인 데이터를 어떻게 뷰에 전달할 것인가? 61
  • 62. UI 레이어 구현, 역할 1 - 서비스에 요청 전달하기• 컨트롤러에서 서비스에 실행 위임 – 서비스의 입력 값 처리 • 커맨드 객체로 받기 (입력 Form으로부터 요청 객체 생성) • 컨트롤러에서 직접 생성@RequestMapping(method = RequestMethod.POST)public String submit(ModelMap modelMap, SelfPerEvalRequest request) { SelfPerEvalResult result = evaluationService.evaluate(request); modelMap.addAttribute("result", result); 커맨드 객체로 사용 return "eval/self/selfPerfResult";}@RequestMapping("/review/returnSelfPerfEval")public String returnPerfEval(@RequestParam("rateeId") String rateeId, ModelMap modelMap) { ReturnResult result = returnEvaluationService.returnSelfPerfEval(rateeId, SecurityUtil.getId()); modelMap.addAttribute("result", result); return "review/returnPerfResult"; 컨트롤러에서 직접 생성} 62
  • 63. UI 레이어 구현, 역할 2 - 데이터 보여주기• UI 레이어에 데이터를 가져오는 기능 구현 – 읽어올 데이터가 많다면, • 컨트롤러와 분리된 별도 객체로 구현 63
  • 64. DataLoadingService의 구현• 도메인 레이어를 이용한 구현 – Repository로부터 필요한 엔티티 구함 • 검색 조건 조합 필요시 Specification을 이용 – 엔티티로부터 UI에 필요한 데이터 추출 • 방법 1, 엔티티를 그대로 리턴하기 • 방법 2, DTO를 이용해서 데이터 전달 • 방법에 따라 트랜잭션 경계가 달라짐 public class DomainEmployeeDataLoader implements EmployeeDataLoader { @Transactional public SomeReturn load(String empId) { Employee emp = employeeRepository.findById(empId); checkNull(emp); return new SomeReturn(emp); } … } 64
  • 65. 데이터 보여줄 때, 도메인 객체의 노출 문제!• 도메인 객체를 그대로 UI 레이어에 노출 시 문제 – 도메인 기능이 노출 → 안정성의 문제 • UI 레이어에서 도메인 객체가 제공하는 기능을 실행하면…. – 레이어를 두는 이유가 사라짐!! – 연관된 객체 접근 시 • JPA의 영속성 컨텍스트 범위가 UI 레이어로 확장되어야 함 • 또는 DataLoader에서 미리 읽어야 함 (연관이 Lazy인 경우)<%-- 도메인 객체 노출 시, 문제 가능성 --%> organization이 Lazy면 트랜잭션 범위 확장 필요<%= employee.getOrganization().getName() %><% employee.changePassword("", "newPassword"); %> OSIV, No! 65
  • 66. 개인적 의견: DTO 내지 상위의 읽기 전용 인터페이스 사용• UI에서 필요로 하는 데이터만 담고 있는 DTO – DataLoader에서 도메인 객체를 DTO로 변환처리 // DataLoader PersonnelEval eval = personnelEvalRepository.findById(id); ViewDataDTO dto = convert(eval); // DTO: 기본 데이터 타입 사용, getter만 제공 return dto;• Getter만 갖는 상위 Interface – 리플렉션 통해서 메서드 실행이 가능하므로 비추 // DataLoader public PersonnelEvalIF load() { // PersonnelEvalIF는 read only 메서드만 제공 PersonnelEval eval = personnelEvalRepository.findById(id); return eval; } 66
  • 67. DTO를 만드는 방법• 1, 도메인 객체에 getter를 만들어서 값 읽어오기 – 구현 쉬움 – 프로퍼티에 대한 getter 메서드 생성 필요 – getter 메서드가 증가• 2, 도메인 객체로부터 값을 받을 수 있는 Builder 를 만들어서 DTO 객체 생성하기 – 구현은 다소 복잡 – 단일 Builder 인터페이스를 이용한 DTO 생성 가능 – getter 메서드의 최소화 67
  • 68. 정리 68
  • 69. Action!• 도메인 모델 개발 – 객체 간 연관으로 사고하는 연습 – ORM(JPA) 이용 매핑 처리• 레이어 간 명확한 역할 구분 – 모든 도메인 로직은 도메인 레이어에 모이도록 • 도메인 로직이 전 레이어에 퍼지지 않도록!• 리포지토리 구현 – Spring Data 이용하면 좀 더 편리 – Spring Data의 Specification 활용 69
  • 70. Q&A 70