3주차 발제
- TDD는 무조건 중요하다.
- 요구사항을 자세히 정리하고 분석하는것이 매우중요!!!!!! 제일 중요!!!!!!!!!!!!
- 설계가 중요한 것들이 필요하다.
- 요구사항을 충족하는 기술들을 잘 선택하는것.
- 요구사항 비기능적으로 영속성, 확장성 등등 -> 나중에 마지막에 구체화(Mysql, 로드밸런스 등등)
- 요구사항이 자세해야, 구조를 파악하고, 도메인을 적용등등 하기가 수월하다
- 특히 개발시간도 매우 단축된다. 도메인 전문가가 원하는 기능이 나올 것이므로.
- (MCP가 aws등등 다)
- (RPG게임 비추, 현실에서 스킬창과아이템이있는데?ㅋㅋ우시게켘ㅋ)
- 요구사항을 자세히 정리하고 분석하는것이 매우중요!!!!!! 제일 중요!!!!!!!!!!!!
- Controller - service ,
- Layered+ clean
- Presentation -> Business <- Datasource 구조로 가자
- Business가 datasource를 의존하지않고, 인터페이스를 통해 의존성 역전하자
- 왜냐면, business의 로직이 제일 중요하기 때문이다. 매우 중요하다. 따라서 datasource 에 따라 business가 변경되는 구조는 안좋은구조기때문에.
- 그리고 반드시 business DTO로 변환하고 도메인에서 활용하자
- controller 의 Request, response를 비즈니스가 의존하면 말도안되는거임.ㄹㅇ
- 보통은 controller-layer, application-layer, domain-layer, repository- layer
- application-layer는
- Facade-layer : 반드시 반드시 service들만을 의존한다.
- service: service및 repository들을 의존한다. 굿굿
- application-layer는
1 | // Presentation Layer (1) |
- 항해할때는, JPA model을 도메인 모델로 써랴..그게 비용이적다.ㅠ 실무에서는 난잘안한다 분리해서 매핑한다.
- DB쓰지말고, 비즈니스로직만 짜라.
Clean + Layered Architecture
- 애플리케이션의 핵심은 비즈니스 로직
- 데이터 계층 및 API 계층이 비즈니스 로직을 의존 ( 비즈니스의 Interface 활용 )
- 도메인 중심적인 계층 아키텍처
- Presentation 은 도메인을 API로 서빙, DataSource 는 도메인이 필요로 하는 기능을 서빙
- DIP 🆗 OCP 🆗
도메인?
- Domain ? Entity ? 이게 뭐요..
용어에서부터 벽이 느껴질 때가 있는데, 바로 위와 같이 여기저기서 쓰이는 용어들입니다. 심지어는 상황에 따라 달라지는 이 용어들로 소통하다보면, 서로 다른 이야기를 하는 경우도 종종 있는 것 같아요. 앞서 우리가 살펴본 소프트웨어 아키텍처 패턴에서 또한 용어의 다른 뜻 때문에 헷갈리는 분들이 많았을 거라고 생각해요. 그래서 좀 보편적으로 이해해보면 좋을 것 같습니다.
도메인
( Domain )
- 특정 기능과 관련된 속성, 기능 등을 응집화시킨 개념
- e.g.
- 도메인 이해도가 높아야 한다. = 해당 기능을 구성하는 하위 도메인에 대한 유기적 흐름 이해가 가능하다.
- 도메인 모델 = 기능적으로 군집화시켜놓은 개념으로 일반적으로는 그 도메인을 표현하는 객체를 의미하며 POJO 일수도, JPA Entity 를 도메인 모델로 사용할 수도 있다.
- 도메인 url = 하위 uri를 그루핑할 수 있는 응집화된 url
엔티티
( Entity )
- 도메인을 설명할때 말하는 엔티티 : 도메인 모델의 맥락에서
특정 주제에 대한 속성 기능을 응집화시켜놓은 도메인 모델
- DB 와 연관지어 말하는 엔티티 : DB 테이블이나 그
테이블에 매핑되는 객체
- 도메인을 설명할때 말하는 엔티티 : 도메인 모델의 맥락에서
그렇다면 도메인 맥락에서 db 엔티티와 분리되어 있다라는 말은 “나는 비즈니스 로직을 표현하기 위한 “도메인 객체”를 DB 의 엔티티와는 상관없게
표현할거야“ 라고 설명할수 있고, 그와 반대로 DB 엔티티 (e.g. JPA Entity) 를 비즈니스의 대상이 되는 도메인 모델
로서 정의할 수도 있겠죠.
만약 도메인 모델 != 엔티티 라는 설계
를 통해 아예 데이터베이스와 별개로 나는 비즈니스 로직을 강한 응집도를 주고, 외부의존성은 내 비즈니스 룰을 따라야 해! 라는 룰을 가져간다면 도메인영역 (핵심 비즈니스 로직) 은 엔티티를 모를테고, 이 경우 엔티티를 통해 db와 상호작용하는 datasource layer는 도메인 -> 엔티티, 엔티티 -> 도메인 의 작업을 수행하여 “내가 아는 언어
” 로 변경해야 합니다.
Clean Code에서 추구하는 것
- 읽기 쉽고 단순한 코드 - 코드는 누구나 쉽게 읽을 수 있어야 하며, 너무 많은 책임을 가지고 있지 않을 것
- 의존성 격리 - 모듈 간 미치는 영향을 최소화하기 위해 의존성 격리를 고려할 것
- 추상화 - 명세와 구현을 적절히 분리할 것
- 중복성 최소화 - 중복된 코드는 최대한 지양할 것
발제 후 Q/A
DB 구현체 없는 방법으로 구현할수있는법? -> 구현안함. 대신 영속화 로직은 고려해야함. domain객체를 만들고, 나중에 repository infra를 작성하니까. domain만 고려하기.
동시성이슈 없이 합니다. -=> 고려하지않아도됨
Facade, service 쓰는법
// interface layer (Request, Reponse) // application layer (Criteria, Result) // domain layer (Command, Info || Domain)| // infra layer
//[코치님께서 제시해주신 방식(?)] @PostMapping("/api/v1/points") public ApiResponse<PointResponse> createPoint(@RequestBody CreatePointRequest request) { Point point = pointService.createPoint(request); // Point 도메인 객체를 직접 반환 return ApiResponse.success(PointResponse.of(point)); }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- controller는 request, reponse
- facade 는 criteria, orderResult -> 복잡하고 종합되는 service가 있을때... 계층 필요함
- service는 command
- 
- facade말고 service 만 쓴다면?
- facade 안쓸거야 Service 다풀거야..
1. Facade(App, UseCase Service
2. Service(UseCase)만 쓴다
- service도 usecase 또는 전체를 통합할수도있음
- 
- Facade to facade는 절대, 참조하지말고, 하위만 참조하기. service to service또한 비추,
- facade 를 사용하면 service에서 상위 facade에서 전달해준 객체를 해주는게 ...?더 낫다고하심
- **controller에서는 facade 또는 service 호출가능함 섞어쓰기**. 나머지는 ..별루 비추
- 다른 도메인이 필요할경우 facade에서 필요한 객체를 매개변수로 받아 service로가져오는거추천,...
- 멘토링 시간에서도 언급해주셨지만, "Interface(Controller) Layer" 에 `Request`, `Response` DTO가 있어야 한다고 말씀 주셨습니다. "Business(Service) Layer" 에서 받은 도메인 객체를 Response DTO **객체를 생성하는 책임**은 Controller에 있다고 보시는 게 맞을까요? 네
-interfaces/ application/ <- 서비스의 파사드 역할을 하겠다. domain/ <- 도메인 서비스와 도메인 객체를 넣겠다. infra/1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
- repository는 반드시 domain쪽에 두기...
- infra에서.
- XXXRepositoryImpl
- XXXJpaRepositoryImpl
- stub상태로 repository 한후ㅜ 테스트
# 3주차 QnA
- 현재 파사드 활용하는 이유
- 유스케이스를 파사드 패턴으로 모아놓은것
- 현재는 실무에서도 XXX파사드, XXX서비스 두가지 비즈니스 역할만 사용 중
- usecase별로 나누면 오히려 복잡한느낌?이였다.
- 포인트 restfulapi -> `/user/{userId}/point` 로 할꺼냐?, `/point/?userId={userId}`, **header에서 userId**
- `/point/` + header 가 보안상 제일맞다.
- restfulapi 고집하다보면 계속 user가 뚱뚱해질 것이다.
- **restfulapi에 너무 집중하지말자.**
- **Domain.toEntity()를 쓰면안된다.**
- 우리는 비즈니스 로직이 외부의 영향을 받지 않도록 설계하고있고, 의존성 역전도 일으켰다.
- domain.toEntity() 코드는 domain클래스에서 infra의 entity를 알아야지만 코드짤수있므로 하지마라. ~~겨우 없앤 의존성이 또 생기잖아~~
- 항상 domain에서는 infra나 presentation과 관련 코드에 대해서 의존성없앨려고, DIP 등등을 활용
- 아래와 같은 방식이면 해결할수있다.
- entity.toDomain()
- Entity.of(domain)
- Request.toCommend() 같은 것도 가능하다
- 다만 값의 형태 전환은 가능하지만 사이에 정책이나 비즈니스 로직을 넣는건 절대 안된다. 도메인 계층에 모든 비즈니스 로직을 모으기로했잖아!
- mapper이므로 to, of 로 쓴다.
- **패키지 구조에 대한 고찰**
- 퍼사드+도메인 서비스가 있는 구조
- facade 가 정확한 응용 서비스의 역할을 정확히 하고있으므로 도메인서비스는 도메인 비즈니스 로직만 표현할수있게됨interfaces/ application/ <- 도메인 서비스 domain/ <- 도메인 객체를 넣겠다. infra/1
2
3
4
5
- 도메인 서비스만 있는 구조
- 도메인 서비스가 반드시 application 계층에있어야한다. 퍼사드가 없기때문에 응용 서비스의 역할도 해야하기 때문이다.
- 예를 들면 주문+결제 서비스인경우-> 한 도메인 서비스로 처리해야하므로1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
- 포인트는 음수가 될수없다는 정책이 controller에 대해 이미 작성되어있는데, 따로 controller말고 다른곳에서도 테스트해야하나?
- 네, 도매인 객체에서 반드시 테스트 필요합니다.
- controller는 단순히 제공되는 인터페이스에서의 인자를 valid해주는것이고,
- 실제로 비즈니스로직은 반드시 도메인모델들이 활용되므로, 반드시 도메인모델에서 정책을 반영 후 테스트도 진행해야한다.
- controller는 도메인과 별도다. 다른 프로토콜로 요청들어오면? valid안된다.
# 3주차 과제 QnA
## 1번 질문
- 주문 구현 시
`OrderFacade` 를 통해서 각 도메인과 도메인 서비스를 각자 역할과 책임을 분리하고자 노력했습니다.
혹시 부족한 부분이나 개선해야할 부분 있을까요? (방향이 잘못되었다거나ㅠㅠ)
- OrderFacade + 각 도메인Service + PayCalculator(도메인 서비스)(추후 할인정책 등등 적용하여 최종 결제 금액 계산책임) 를 조합하여, Facade를 구현해보았습니다.
- 참고사항: 현재 구조에서 아래 원칙을 이용했으며, 아직 DTO 명명규칙은 자세히 적용하지 못했습니다.
- 클린 아키텍처로 작성하며, service -> infra는 의존성 역전을 적용한다.
- Facade는 응용 계층이다. 여러 도메인 service 를 통합한다.
- 도메인 비즈니스 로직은 도메인계층의 도메인모델과 도메인 서비스를 이용하자.
- 각 계층의 DTO 명명규칙
- Presentation는 request = "~Request", response = "~Response"
- Facade는 request = "~Criteria", response = "~Result"
- Service 계층은 request = "~Command", "~Info", response = "~Domain", "~Vo"
```kotlin
fun processOrder(order: Order) {
// 1. user 검증
userService.checkActiveUser(order.userId)
// 2. 상품 준비 상태로 전환
val orderInPreparation = orderService.transitionToProductReady(order)
// 3. 상품 재고 확인 및 차감
productService.saleProcessBy(order.orderLines)
// 4. 결제 필요 금액 계산
val finalAmount = order.issuedCouponId.let { couponId ->
val issuedCouponAndCoupon = couponService.findByIssuedCouponId(order.issuedCouponId)
payCalculator.calculateFinalAmount(order, issuedCouponAndCoupon)
}
// 5. 결제 대기 상태로 전환
val orderReadyForPayment = orderService.transitionToPaymentReady(orderInPreparation)
// 6. 포인트 조회 및 결제 처리
val point = pointService.getPoint(order.userId)
val paymentResult = paymentService.processPayment(orderReadyForPayment, point, finalAmount)
// 7. 결제 결과에 따른 처리
val completedOrder = orderService.transitionToPaymentComplete(orderReadyForPayment)
}
1번 답변
1 | 각 레이어의 네이밍에 이유를 가지고 명명해주면 좋겠다. |
fun order(order: Order) { // 1. user 검증 userService.checkActiveUser(order.userId) // 2. 상품 준비 상태로 전환 val orderInPreparation = orderService.transitionToProductReady(order) // 3. 상품 재고 확인 및 차감 productService.saleProcessBy(order.orderLines) // 4. 결제 필요 금액 계산 val finalAmount = order.issuedCouponId.let { couponId -> val issuedCouponAndCoupon = couponService.findByIssuedCouponId(order.issuedCouponId) payCalculator.calculateFinalAmount(order, issuedCouponAndCoupon) } // 5. 결제 대기 상태로 전환 val orderReadyForPayment = orderService.transitionToPaymentReady(orderInPreparation) // 6. 포인트 조회 및 결제 처리 val point = pointService.getPoint(order.userId) val paymentResult = paymentService.processPayment(orderReadyForPayment, point, finaAmount) // 7. 결제 결과에 따른 처리 val completedOrder = orderService.transitionToPaymentComplete(orderReadyForPayment) } fun order(cri: OrderCriteria.Order) { // orderProduct {productId: 1, quantity: 20, price: 2000} val products = productService.getProducts(cri.productIds); val coupon = couponService.useCopon({cri.coupon, productId}); val orderCommand = OrderCommand(...); // coupon_id val order = orderService.order(orderCommand); val payment = paymentService.pay(order.totalPrice); return order } orderComand() // 쿠폰 종류에는 2가지 있어요. order { couponId: 123 // 장바구니 쿠폰 } order_item { couponId: 123124214 // 상품 쿠폰 } <!--6-->
유저가 사용하는 서비스의 메소드들에 대해 UseCase라고 할 수 있음 유저가 사용하지 않으면 UseCase가 아님
디자인 패턴의 파사드 패턴을 찾아보면 좋겠다.
일반적인 서비스에서는 주문서를 만들고 결제를 함 결제는 PG에서 진행, 결제시 콜백 URL을 받았다가 결제 완료시 callback URL로 결과를 넘겨줌 그 때 주문 완료라고 업데이트 ⇒ 따라서 파사드를 사용하지 않는 경우가 많음
정기결제를 하는 경우 ⇒ 파사드 필요, 주문서 만들고, 결제까지 한번에 하기 때문
파사드 또한 UseCase 중 하나
4번 질문
- 개발 진행 시 어떤 레이어 순서대로 개발을 진행하는 걸 추천 하시나요?
4번 답변
- 컨트롤러 인터페이스 레이어부터 →..→ 도메인
5번 질문
- 선착순 쿠폰 발급을 구현할때 설계를 RDB이용 대기열을 구현해서 하려고 합니다. 이부분에서 있어서 예상되는 문제점이 있을까요?
5번 답변
대기열은 시작하는 관점에서는 과할 수 있다 ⇒ 대규모의 순간적인 트래픽을 처리하기 위해 사용함 ⇒ 평상시 트래픽에서는 사용하지 않음 (그 시간에 다른 피처 쳐야할 것이다)
DB입장에서는 대부분의 동작이 읽기 쓰기로 정리가된다. ⇒ 해보지 않았더라도, 수단은 알아두는 것이 좋음 ⇒ 면접시 내가 안해봤음에도 잘 할 수 있음을 느끼게 해야함 ⇒ 그러려면 자기 확신이 필요함 (공부 열심히 하자)
6번 질문
- 헥사고날 아키텍처는 개발자로 하여금, 강제로 Core(응용서비스 + 도메인 서비스 및 도메인)가 외부의 조건으로 인하여, 변경을 최소화 할수있는 아키텍처로 알고있습니다. 하지만 실무에는 적용이 잘 안되는것으로 알고있습니다.
7번 답변
- 당근에서도 헥사고날을 쓰고, 쓰는 회사들이 있긴함 멘토님 주변의 고수들은 잘 안쓰는 것 같다 파일이 너무 많아져서 귀찮아지기 때문? 허접들이 상황을 많이 따짐 (스트레스 많이 받음), 고수는 똥같은 코드가 있어도 스트레스 안받고 잘 함
8번 질문
- XXXFacade를 만들때는 2개 이상 Service를 넣어서 비즈니스 로직을 만들때일 것 같은데 이 경우 Facade에서도 단위테스트를 진행하나요 ?
8번 답변
파사드에서 단위테스트는 하지 않음 결국 파라미터로 실행하는 것
파사드에서 에러가 발생하는 것은 파사드의 목적이 잘못된 것 아닌가.
인터페이스를 완벽하게 설계했다면 잘 되어야한다.
9번 질문
- Exception을 도메인마다 선언했는데 공통적으로 사용함으로서 가지는 이점도 있을 것 같습니다. 이럴때 Shared에 Common Exception을 추가해서 각 도메인에서 extends하는 방식도 괜찮을까요?
9번 답변
공통적으로 만들어서 사용하고 Support에서 관리함
CommonExpcetion class BizExpcetion(code: ErrorCode ): RuntimeException() { } class NotFoundEscpetion: BizExpcetion() {} class NotFoundEscpetion: BizExpcetion() {} class NotFoundEscpetion: BizExpcetion() {} class NotFoundEscpetion: BizExpcetion() {} enum ErrorCode { "유저조회실패", }
10번 질문
- 파사드에서는 분기처리를 거의 사용하지 않는다고 보면 될까요?
10번 답변
- 파사드랑 서비스에서 private, 분기 최대한 사용하지 않음 최대한 사용하지 않는 쪽으로 기울려고함
꿀팁
설계 문서를 작성하는 경우 -> 매우 중요하다 생각
- 처음 개발, 차세대 개발, 통합하는 개발
- 통합하는 개발의 경우
- 각 컴포넌트의 공통점과 차이점을 보고 High level에서 봄
- 도메인 객체가 변화하는 흐름이 있음 (상태머신 다이어그램 사용)
- 회의를 리드하는 역량을 키워야함
- 로지컬 레벨을 이야기하고 있는데, 구현체 이야기하는 경우 밴시킴
- 회의를 시작하기 전에 뭐를 결정해야하는지 미리 말해줌 이 회의에서는 이 문서가 나와야하는지
- 이게 별거 아니어보여도 진짜 잘해보임
- 반드시 지켜줘야하는 것
- DIP, 구현해오는것, 단위테스트
- 구조는 크게 고려할 필요 없음
- 메소드를 잘게 쪼개어도 fail요소는 아님