업데이트
This commit is contained in:
121
README.md
121
README.md
@@ -1,6 +1,28 @@
|
|||||||
# 예제 - 음식배달
|
# 예제 - 음식배달
|
||||||
|
|
||||||
MSA/DDD/Event Storming/EDA 를 포괄하는 분석/설계/구현/운영 전단계의 예제입니다.
|
본 예제는 MSA/DDD/Event Storming/EDA 를 포괄하는 분석/설계/구현/운영 전단계를 커버하도록 구성한 예제입니다.
|
||||||
|
이는 클라우드 네이티브 애플리케이션의 개발에 요구되는 체크포인트들을 통과하기 위한 예시 답안을 포함합니다.
|
||||||
|
- 체크포인트 : https://workflowy.com/s/assessment-check-po/T5YrzcMewfo4J6LW
|
||||||
|
|
||||||
|
|
||||||
|
# Table of contents
|
||||||
|
|
||||||
|
- [예제 - 음식배달](#---)
|
||||||
|
- [서비스 시나리오](#-)
|
||||||
|
- [체크포인트](#)
|
||||||
|
- [분석/설계](#)
|
||||||
|
- [구현:](#)
|
||||||
|
- [DDD 의 적용](#ddd--)
|
||||||
|
- [폴리글랏 퍼시스턴스](#-)
|
||||||
|
- [폴리글랏 프로그래밍](#-)
|
||||||
|
- [동기식 호출 과 Fallback 처리](#---fallback-)
|
||||||
|
- [비동기식 호출 / 시간적 디커플링 / 장애격리 / 최종 (Eventual) 일관성 테스트](#---------eventual--)
|
||||||
|
- [운영](#)
|
||||||
|
- [CI/CD 설정](#cicd-)
|
||||||
|
- [동기식 호출 / 서킷 브레이킹 / 장애격리](#------)
|
||||||
|
- [오토스케일 아웃](#-)
|
||||||
|
- [무정지 재배포](#-)
|
||||||
|
- [신규 개발 조직의 추가](#---)
|
||||||
|
|
||||||
## 서비스 시나리오
|
## 서비스 시나리오
|
||||||
|
|
||||||
@@ -29,8 +51,6 @@ MSA/DDD/Event Storming/EDA 를 포괄하는 분석/설계/구현/운영 전단
|
|||||||
|
|
||||||
## 체크포인트
|
## 체크포인트
|
||||||
|
|
||||||
https://workflowy.com/s/assessment-check-po/T5YrzcMewfo4J6LW
|
|
||||||
|
|
||||||
- 분석 설계
|
- 분석 설계
|
||||||
- 이벤트스토밍:
|
- 이벤트스토밍:
|
||||||
- 스티커 색상별 객체의 의미를 제대로 이해하여 헥사고날 아키텍처와의 연계 설계에 적절히 반영하고 있는가?
|
- 스티커 색상별 객체의 의미를 제대로 이해하여 헥사고날 아키텍처와의 연계 설계에 적절히 반영하고 있는가?
|
||||||
@@ -143,14 +163,6 @@ public class 결제이력 {
|
|||||||
private String orderId;
|
private String orderId;
|
||||||
private Double 금액;
|
private Double 금액;
|
||||||
|
|
||||||
@PrePersist
|
|
||||||
public void onPrePersist(){
|
|
||||||
결제승인됨 결제승인됨 = new 결제승인됨();
|
|
||||||
BeanUtils.copyProperties(this, 결제승인됨);
|
|
||||||
결제승인됨.publish();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Long getId() {
|
public Long getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@@ -290,21 +302,22 @@ public interface 결제이력Service {
|
|||||||
```
|
```
|
||||||
|
|
||||||
- 동기식 호출에서는 호출 시간에 따른 타임 커플링이 발생하며, 결제 시스템이 장애가 나면 주문도 못받는다는 것을 확인:
|
- 동기식 호출에서는 호출 시간에 따른 타임 커플링이 발생하며, 결제 시스템이 장애가 나면 주문도 못받는다는 것을 확인:
|
||||||
|
|
||||||
|
|
||||||
```
|
```
|
||||||
# 결제 서비스를 잠시 내려놓음
|
# 결제 (pay) 서비스를 잠시 내려놓음 (ctrl+c)
|
||||||
kill (lsof -i)
|
|
||||||
|
|
||||||
#주문처리
|
#주문처리
|
||||||
http localhost:8080/주문s 품목=통닭 수량=1 주소=서울 #오류
|
http localhost:8081/orders item=통닭 storeId=1 #Fail
|
||||||
http localhost:8080/주문s 품목=피자 수량=2 주소=서울 #오류
|
http localhost:8081/orders item=피자 storeId=2 #Fail
|
||||||
|
|
||||||
#결제서비스 재기동
|
#결제서비스 재기동
|
||||||
cd 결제
|
cd 결제
|
||||||
mvn spring-boot:run
|
mvn spring-boot:run
|
||||||
|
|
||||||
#주문처리
|
#주문처리
|
||||||
http localhost:8080/주문s 품목=통닭 수량=1 주소=서울 #성공
|
http localhost:8081/orders item=통닭 storeId=1 #Success
|
||||||
http localhost:8080/주문s 품목=피자 수량=2 주소=서울 #성공
|
http localhost:8081/orders item=피자 storeId=2 #Success
|
||||||
```
|
```
|
||||||
|
|
||||||
- 또한 과도한 요청시에 서비스 장애가 도미노 처럼 벌어질 수 있다. (서킷브레이커, 폴백 처리는 운영단계에서 설명한다.)
|
- 또한 과도한 요청시에 서비스 장애가 도미노 처럼 벌어질 수 있다. (서킷브레이커, 폴백 처리는 운영단계에서 설명한다.)
|
||||||
@@ -315,24 +328,86 @@ http localhost:8080/주문s 품목=피자 수량=2 주소=서울 #성공
|
|||||||
### 비동기식 호출 / 시간적 디커플링 / 장애격리 / 최종 (Eventual) 일관성 테스트
|
### 비동기식 호출 / 시간적 디커플링 / 장애격리 / 최종 (Eventual) 일관성 테스트
|
||||||
|
|
||||||
|
|
||||||
|
결제가 이루어진 후에 상점시스템으로 이를 알려주는 행위는 동기식이 아니라 비 동기식으로 처리하여 상점 시스템의 처리를 위하여 결제주문이 블로킹 되지 않아도록 처리한다.
|
||||||
|
|
||||||
|
- 이를 위하여 결제이력에 기록을 남긴 후에 곧바로 결제승인이 되었다는 도메인 이벤트를 카프카로 송출한다(Publish)
|
||||||
|
|
||||||
|
```
|
||||||
|
package fooddelivery;
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
@Table(name="결제이력_table")
|
||||||
|
public class 결제이력 {
|
||||||
|
|
||||||
|
...
|
||||||
|
@PrePersist
|
||||||
|
public void onPrePersist(){
|
||||||
|
결제승인됨 결제승인됨 = new 결제승인됨();
|
||||||
|
BeanUtils.copyProperties(this, 결제승인됨);
|
||||||
|
결제승인됨.publish();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- 상점 서비스에서는 결제승인 이벤트에 대해서 이를 수신하여 자신의 정책을 처리하도록 PolicyHandler 를 구현한다:
|
||||||
|
|
||||||
```
|
```
|
||||||
#상점 서비스를 잠시 내려놓음
|
package fooddelivery;
|
||||||
kill (lsof -i )
|
|
||||||
|
...
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class PolicyHandler{
|
||||||
|
|
||||||
|
@StreamListener(KafkaProcessor.INPUT)
|
||||||
|
public void whenever결제승인됨_주문정보받음(@Payload 결제승인됨 결제승인됨){
|
||||||
|
|
||||||
|
if(결제승인됨.isMe()){
|
||||||
|
System.out.println("##### listener 주문정보받음 : " + 결제승인됨.toJson());
|
||||||
|
// 주문 정보를 받았으니, 요리를 슬슬 시작해야지..
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
실제 구현을 하자면, 카톡 등으로 점주는 노티를 받고, 요리를 마친후, 주문 상태를 UI에 입력할테니, 우선 주문정보를 DB에 받아놓은 후, 이후 처리는 해당 Aggregate 내에서 하면 되겠다.:
|
||||||
|
|
||||||
|
```
|
||||||
|
@Autowired 주문관리Repository 주문관리Repository;
|
||||||
|
|
||||||
|
@StreamListener(KafkaProcessor.INPUT)
|
||||||
|
public void whenever결제승인됨_주문정보받음(@Payload 결제승인됨 결제승인됨){
|
||||||
|
|
||||||
|
if(결제승인됨.isMe()){
|
||||||
|
카톡전송(" 주문이 왔어요! : " + 결제승인됨.toString(), 주문.getStoreId());
|
||||||
|
|
||||||
|
주문관리 주문 = new 주문관리();
|
||||||
|
주문.setId(결제승인됨.getOrderId());
|
||||||
|
주문관리Repository.save(주문);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
상점 시스템은 주문/결제와 완전히 분리되어있으며, 이벤트 수신에 따라 처리되기 때문에, 상점시스템이 유지보수로 인해 잠시 내려간 상태라도 주문을 받는데 문제가 없다:
|
||||||
|
```
|
||||||
|
# 상점 서비스 (store) 를 잠시 내려놓음 (ctrl+c)
|
||||||
|
|
||||||
#주문처리
|
#주문처리
|
||||||
http localhost:8080/주문s 품목=통닭 수량=1 주소=서울
|
http localhost:8081/orders item=통닭 storeId=1 #Success
|
||||||
http localhost:8080/주문s 품목=피자 수량=2 주소=서울
|
http localhost:8081/orders item=피자 storeId=2 #Success
|
||||||
|
|
||||||
#주문상태 확인
|
#주문상태 확인
|
||||||
http localhost:8080/주문s # 주문상태 안바뀜 확인
|
http localhost:8080/orders # 주문상태 안바뀜 확인
|
||||||
|
|
||||||
#상점 서비스 기동
|
#상점 서비스 기동
|
||||||
cd 상점
|
cd 상점
|
||||||
mvn spring-boot:run
|
mvn spring-boot:run
|
||||||
|
|
||||||
#주문상태 확인
|
#주문상태 확인
|
||||||
http localhost:8080/주문s # 모든 주문의 상태가 배송됨으로 확인
|
http localhost:8080/orders # 모든 주문의 상태가 "배송됨"으로 확인
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -10,12 +10,18 @@ import org.springframework.stereotype.Service;
|
|||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class PolicyHandler{
|
public class PolicyHandler{
|
||||||
|
|
||||||
|
@Autowired 주문관리Repository 주문관리Repository;
|
||||||
|
|
||||||
@StreamListener(KafkaProcessor.INPUT)
|
@StreamListener(KafkaProcessor.INPUT)
|
||||||
public void whenever결제승인됨_주문정보받음(@Payload 결제승인됨 결제승인됨){
|
public void whenever결제승인됨_주문정보받음(@Payload 결제승인됨 결제승인됨){
|
||||||
|
|
||||||
if(결제승인됨.isMe()){
|
if(결제승인됨.isMe()){
|
||||||
System.out.println("##### listener 주문정보받음 : " + 결제승인됨.toJson());
|
System.out.println("##### listener 주문정보받음 : " + 결제승인됨.toJson());
|
||||||
|
|
||||||
|
주문관리 주문 = new 주문관리();
|
||||||
|
주문.setId(결제승인됨.getOrderId())
|
||||||
|
주문관리Repository.save(주문);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@StreamListener(KafkaProcessor.INPUT)
|
@StreamListener(KafkaProcessor.INPUT)
|
||||||
|
|||||||
Reference in New Issue
Block a user