4. 화면 중심 프로젝트
• 화면 중심으로 프로젝트 구조를 구성
• View 단위로 개발자 투입
• Service, Dao, Entity를 각자 만들어 써야
함, 공유하는 것은 허용하지 않음
5. 회원관리 화면 Package
controller (MemberController)
service (MemberService)
dao (MemberDao)
entity (Member)
상품 관리 화면 Package
controller (ProductController)
service (ProductService)
dao (ProductDao)
entity (Product)
39. ORM에 관한 오해
Q : OBJECT DB같은 것 아닌가요? 이미 망
한 기술로 알고 있는데요?
A : OBJECT DB가 아닙니다. ORM은 RDB
를 사용해야 한다는 것을 인정하고 RDB를
객체지향 적으로 사용 할 수 있도록 중간
역할을 하기위해 만들어 졌습니다. 그래서
이름도 Object-Relational-Mapping 입니다.
40. ORM에 관한 오해
Q : 성능이 느리다.
A : 지금 JAVA가 느리다고 하는 것과 비슷합니다.
잘 알고 쓰게되면 SQL보다 최적화 하기가 쉽습
니다. 가장 중요한 것은 파레토의 법칙입니다. 때
로는 순수SQL을 써서 최적화 해야 하는 경우도
있습니다.
예) 여러 Table에 insert시 Hibernate는 한번에
batch insert를 시도함, 1차 케시를 통해 한번 조회
한 데이터 조회시 메모리에서 읽어옴
41. ORM에 관한 오해
Q : ORM을 사용하면 SQL을 못쓴다.
A : 이거야 말로... ORM도 결국은 SQL을 작
성해서 DB 와 통신하게 됩니다. 필요하면
언제든지 직접 SQL을 작성 할 수 있습니
다. 물론 myBatis 같은 것과 함께 사용하는
것도 가능합니다.
42. ORM에 관한 오해
Q : SQL을 몰라도 되나요?
A : 오히려 SQL에 관해서 잘 알아야 합니
다. 최종적으로는 SQL이 생성되니까요.
43. ORM에 관한 오해
Q : 공부할 내용이 많으면 어쩌지...
A : 이건 인정합니다. --; 중요한 것은 ORM
의 철학을 이해하는 것입니다. 하지만 이
것저것 다 떠나서 단순하게 사용하더라도
충분히 유용합니다. 궁극적으로는 ORM을
활용해서 제대로 된 DDD(도메인 주도 개
발)를 하는 것이 목표라고 생각합니다.
44. ORM에 관한 오해
Q : 모두 iBatis, myBatis만 사용하는데요?
A : 진실은?
45. 2012년 8월 한국 구글 검색
JPA
8%
Hibernate
12% myBatis
31%
iBatis
49%
myBatis iBatis Hibernate JPA
46. 2012년 8월 인도 구글 검색
iBatis
JPA 3%
11%
Hibernate
86%
myBatis iBatis Hibernate JPA
47. 2012년 8월 미국 구글 검색
myBatis
iBatis
2% 4%
JPA
16%
Hibernate
78%
myBatis iBatis Hibernate JPA
48. 2012년 8월 전세계 구글 검색
myBatis
iBatis
3% 3%
JPA
33%
Hibernate
61%
myBatis iBatis Hibernate JPA
52. 멤버십 프로젝트
• 사용된 기술 : JPA2, Hibernate, Spring-Data-
JPA, QueryDSL, Spring, Maven
• source : https://github.com/holyeye/
devon2012
53. 도메인 모델
1 :N N :1 카드
회원 회원카드
적립률
1
:
N Create
카드적립 포인트
54. 도메인 객체
@Getter @Setter
@Entity
public class Member extends BaseEntity<Long>{
private String name;
private int age;
@OneToMany(mappedBy="member")
List<MemberCard> memberCards = new ArrayList<MemberCard>();
public void addMemberCard(MemberCard memberCard) {
memberCards.add(memberCard); @Getter @Setter
} @Entity
} public class Card extends BaseEntity<Long>{
private String name;
private int rate; //적립률
//create
public CardPoint createCardPoint(int money) {
return new CardPoint(name,money,rate);
}
}
55. @Getter @Entity
public class MemberCard extends BaseEntity<Long>{
@OneToMany(cascade=CascadeType.PERSIST) @JoinColumn(name="memberCard_id")
private List<CardPoint> cardPoints = new ArrayList<CardPoint>();
@ManyToOne private Member member;
@ManyToOne private Card card;
//BIZ
public void payMoney(int money) {
CardPoint createCardPoint = card.createCardPoint(money);
addCardPoint(createCardPoint);
}
//VIEW Logic
public int getTotalPoint() {
int totalPoint = 0;
for (CardPoint cardPoint : cardPoints) {
totalPoint += cardPoint.getPoint();
}
return totalPoint;
}
}
56. 회원 조회, QueryDSL
@RequestMapping("member/home")
public String home(MemberCond cond, Model model) {
//QUERY DSL
QMember qMember = QMember.member;
BooleanExpression containsName = qMember.name.contains(cond.getName());
BooleanExpression gtAge = qMember.age.gt(cond.getAge());
model.addAttribute("members",memberRepository.findAll(containsName.and(gtAge)));
return "member/home";
} public interface MemberRepository extends
JpaRepository<Member, Long>, QueryDslPredicateExecutor<Member>{
}
select * from Member member
where (member.name like '%서%') and member.age > 26
57. 회원 등록, SpringDataJPA
`
@RequestMapping("member/saveMember")
public String saveMenu(Member member) {
memberRepository.save(member);
return "redirect:/member/home";
}
public interface MemberRepository extends
JpaRepository<Member, Long>, QueryDslPredicateExecutor<Member>{
}
58. 회원 수정, Web Binding
/member/memberUpdateForm?id= 1
Member
@RequestMapping("member/memberUpdateForm")
public String memberUpdateForm(@RequestParam("id") Member member, Model model) {
model.addAttribute("member", member);
return "member/memberSaveForm";
}
61. 회원카드 결제하기 = ORM 저장
@RequestMapping("memberCard/payMoney")
public String payMoney(
@RequestParam("memberCardId") MemberCard memberCard,
@RequestParam("money") int money) {
`
memberCardService.payMoney(memberCard, money);
return "redirect:/memberCard/home";
}
@Service
@Transactional
public class MemberCardService {
public void payMoney(MemberCard memberCard, int money) {
`
memberCard.payMoney(money);
}
public class MemberCard {
}
@OneToMany(cascade=CascadeType.PERSIST) @JoinColumn(name="memberCard_id")
private List<CardPoint> cardPoints = new ArrayList<CardPoint>();
public void payMoney(int money) {
CardPoint createCardPoint = card.createCardPoint(money);
cardPoints.add(createCardPoint);
}
public class Card{
private String name;
Auto SAVE
private int rate;
public CardPoint createCardPoint(int money) {
return new CardPoint(name,money,rate);
}
}
62. Spring-Data-JPA 소개
• interface로 Repository 자동 생성
(CRUD, 조회)
• simple queries
ex) findByName(String name)
select * from member where name={name}
• pagination
• Web Binding
• Specifications, QueryDSL지원
67. Domain Model
Everywhere
View Controller Service Repository
King
Entity
+Getter,Setter
+BizLogic
+ViewLogic
68. Domain Model
Everywhere
• Domain Model을 모든 Layer에서 적극 사
용하자.
• OSIV(Open Session In View) 사용
• Getter, Setter 사용
• DTO는 중복 악마다!
69. Domain Model
Everywhere
• 장점
• 개발하기에 빠르고 편리함
• 단점
• 복잡한 View 표현 힘듬
• Domain Model의 Getter, Setter를 노출,View로직 추가
• Domain Model의 순수성이 떨어짐
• XML, JSON, 외부와의 통신API 위험 = 도메인 모델은 생
각보다 자주 변함
70. Domain Model
Everywhere
View Controller Service Repository
King
Entity
+Getter,Setter
+BizLogic
+ViewLogic
71. Pure Domain Model
View Controller Service Repository
DTO
Converter
Entity
+Getter,Setter
+BizLogic
+ViewLogic
72. Pure Domain Model
• 도메인모델에 Getter, Setter는 지옥행
• 도메인모델에 View로직은 지옥행
• OSIV(Open Session In View)는 지옥행
• 도메인모델을 객체답게 만들어라!
77. Pure Domain Model
잡종
이런 잡종 구조는 새로운 함수는 물론이고
새로운 자료 구조도 추가하기 어렵다.
양쪽 세상에서 단점만 모아 놓은 구조다.
그러므로 잡종 구조는 되도록 피하는 편이
좋다.
- Clean Code, Chapter 6 객체와 자료구조 -
92. 항상 DTO를 만드는 것은
실용적이지 않습니다.
항상 DTO를 만들면 도메인 객체에 Getter, Setter만들 필요가 없고,VIEW로직 필요 없고
순도 100% 도메인 객체를 유지가능 합니다, 하지만 DTO를 아주 많이 만들어야 하고 또한
ENTITY를 DTO로 바꾸고 DTO를 ENTITY로 변경해야 하는 컨버팅 필요. 관리 포인트가 3가지로
늘어남. 아주 괴로움. 때로는 배보다 배꼽이 큽니다!
93. 결론
Domain Model Everywhere 로 시작
Domain Model로 처리하기 힘든
복잡하고 특별한 경우에는
CQRS, DTO를 사용
Domain Model Everywhere는 진짜 실용적이고 개발하기 편합니다.
DTO가 필수인 경우 = ex) 복잡한 View, [json, xml, 외부노출 API]
CQRS, DTO, Domain Model Everywhere는 프로젝트 규모와 상황을 판단해서 적용해야 합니다.
지극히 주관적인 생각으로는 프로젝트 시작시 Domain Model Everywhere로 시작하고 CQRS, DTO는 필요성을 느끼게
되면 추가해 가는 것이 좋다고 생각합니다. 순도 100%는 아니지만 80%는 지킬 수 있다고 생각합니다.
94. 우리의 과제
• Domain Driven Design
• Pure Domain Model 을 유지하며 실용적
으로 개발하는 방법
• DTO를 생성하는 방법, 활용법
• ex) http://modelmapper.org/
• ex) Builder Pattern
95. 좋은 자료
• 이터너티님의 블로그(http://aeternum.egloos.com)
• 백기선님 블로그 (http://whiteship.me)
• 이미 2005년에 작성된 토비님의 아티클
Domain Model vs. DTO : http://toby.epril.com/?p=99
• 실전 코드가 있는 자바지기님의 DTO에 관한 최
근 아티클 : http://www.slipp.net/wiki/pages/viewpage.action?pageId=2031636
• Javacan님의 DDD 발표자료 : http://javacan.tistory.com/entry/DDD-%EC
%8C%A9%EA%B8%B0%EC%B4%88-%EC%84%B8%EB%AF%B8%EB%82%98-%EB%B0%9C%ED%91%9C-%EC%9E%90%EB%A3%8C
96. 참고
• Clean Code (Chapter 6 객체와 자료 구조)
• http://codebetter.com/gregyoung/
2010/02/16/cqrs-task-based-uis-event-
sourcing-agh/
• http://martinfowler.com/bliki/CQRS.html
109. 뚱뚱한 Service
View A
회원ENTITY
BIZ LOGIC
+ViewA로직
View B
+ViewB로직
+ViewC로직
.....
View C
그래 나 뚱뚱하다!
110. 뚱뚱한 Service
View A
회원ENTITY
BIZ LOGIC 회원Service
+ViewA로직 +회원등록()
View B
+ViewB로직 +회원수정()
+ViewC로직 +회원탈퇴()
..... +getMember()
View C
그래 나 뚱뚱하다!
111. 뚱뚱한 Service
View A 회원ENTITY
BIZ LOGIC 회원Service
+회원등록()
+회원수정()
View B +회원탈퇴()
회원DTO +getMemberDTO()
+ViewA로직
View C +ViewB로직
+ViewC로직
112. 뚱뚱한 Service
View A
회원ENTITY
BIZ LOGIC 회원Service
+회원등록()
+회원수정()
View B +회원탈퇴()
회원DTO
+ViewA로직 +getMemberDTO()
+ViewB로직 +getMemberManageDTO()
View C +ViewC로직 ...DTO()
그래 나 뚱뚱하다!
View D 회원관리DTO
+ViewD로직
115. 뚱뚱한 Service
View A
회원ENTITY
회원Service
BIZ LOGIC
+회원등록()
+회원수정()
View B +회원탈퇴()
회원DTO
+getMemberDTO()
+ViewA로직
+getMemberManageDTO()
+ViewB로직
...DTO()
View C +ViewC로직
그래 나 뚱뚱하다!
View D 회원관리DTO
+ViewD로직
116. 뚱뚱한 Service
View A
회원ENTITY
BIZ LOGIC 회원CommandService
+회원등록()
+회원수정()
View B +회원탈퇴()
회원DTO
+ViewA로직
+ViewB로직
View C +ViewC로직
회원QueryService
+getMemberDTO()
+getMemberManageDTO()
회원관리DTO ...DTO()
View D
+ViewD로직