업데이트
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 Double 금액;
|
||||
|
||||
@PrePersist
|
||||
public void onPrePersist(){
|
||||
결제승인됨 결제승인됨 = new 결제승인됨();
|
||||
BeanUtils.copyProperties(this, 결제승인됨);
|
||||
결제승인됨.publish();
|
||||
}
|
||||
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
@@ -290,21 +302,22 @@ public interface 결제이력Service {
|
||||
```
|
||||
|
||||
- 동기식 호출에서는 호출 시간에 따른 타임 커플링이 발생하며, 결제 시스템이 장애가 나면 주문도 못받는다는 것을 확인:
|
||||
|
||||
|
||||
```
|
||||
# 결제 서비스를 잠시 내려놓음
|
||||
kill (lsof -i)
|
||||
# 결제 (pay) 서비스를 잠시 내려놓음 (ctrl+c)
|
||||
|
||||
#주문처리
|
||||
http localhost:8080/주문s 품목=통닭 수량=1 주소=서울 #오류
|
||||
http localhost:8080/주문s 품목=피자 수량=2 주소=서울 #오류
|
||||
http localhost:8081/orders item=통닭 storeId=1 #Fail
|
||||
http localhost:8081/orders item=피자 storeId=2 #Fail
|
||||
|
||||
#결제서비스 재기동
|
||||
cd 결제
|
||||
mvn spring-boot:run
|
||||
|
||||
#주문처리
|
||||
http localhost:8080/주문s 품목=통닭 수량=1 주소=서울 #성공
|
||||
http localhost:8080/주문s 품목=피자 수량=2 주소=서울 #성공
|
||||
http localhost:8081/orders item=통닭 storeId=1 #Success
|
||||
http localhost:8081/orders item=피자 storeId=2 #Success
|
||||
```
|
||||
|
||||
- 또한 과도한 요청시에 서비스 장애가 도미노 처럼 벌어질 수 있다. (서킷브레이커, 폴백 처리는 운영단계에서 설명한다.)
|
||||
@@ -315,24 +328,86 @@ http localhost:8080/주문s 품목=피자 수량=2 주소=서울 #성공
|
||||
### 비동기식 호출 / 시간적 디커플링 / 장애격리 / 최종 (Eventual) 일관성 테스트
|
||||
|
||||
|
||||
결제가 이루어진 후에 상점시스템으로 이를 알려주는 행위는 동기식이 아니라 비 동기식으로 처리하여 상점 시스템의 처리를 위하여 결제주문이 블로킹 되지 않아도록 처리한다.
|
||||
|
||||
- 이를 위하여 결제이력에 기록을 남긴 후에 곧바로 결제승인이 되었다는 도메인 이벤트를 카프카로 송출한다(Publish)
|
||||
|
||||
```
|
||||
package fooddelivery;
|
||||
|
||||
@Entity
|
||||
@Table(name="결제이력_table")
|
||||
public class 결제이력 {
|
||||
|
||||
...
|
||||
@PrePersist
|
||||
public void onPrePersist(){
|
||||
결제승인됨 결제승인됨 = new 결제승인됨();
|
||||
BeanUtils.copyProperties(this, 결제승인됨);
|
||||
결제승인됨.publish();
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
- 상점 서비스에서는 결제승인 이벤트에 대해서 이를 수신하여 자신의 정책을 처리하도록 PolicyHandler 를 구현한다:
|
||||
|
||||
```
|
||||
#상점 서비스를 잠시 내려놓음
|
||||
kill (lsof -i )
|
||||
package fooddelivery;
|
||||
|
||||
...
|
||||
|
||||
@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:8080/주문s 품목=피자 수량=2 주소=서울
|
||||
http localhost:8081/orders item=통닭 storeId=1 #Success
|
||||
http localhost:8081/orders item=피자 storeId=2 #Success
|
||||
|
||||
#주문상태 확인
|
||||
http localhost:8080/주문s # 주문상태 안바뀜 확인
|
||||
http localhost:8080/orders # 주문상태 안바뀜 확인
|
||||
|
||||
#상점 서비스 기동
|
||||
cd 상점
|
||||
mvn spring-boot:run
|
||||
|
||||
#주문상태 확인
|
||||
http localhost:8080/주문s # 모든 주문의 상태가 배송됨으로 확인
|
||||
http localhost:8080/orders # 모든 주문의 상태가 "배송됨"으로 확인
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -10,12 +10,18 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class PolicyHandler{
|
||||
|
||||
@Autowired 주문관리Repository 주문관리Repository;
|
||||
|
||||
@StreamListener(KafkaProcessor.INPUT)
|
||||
public void whenever결제승인됨_주문정보받음(@Payload 결제승인됨 결제승인됨){
|
||||
|
||||
if(결제승인됨.isMe()){
|
||||
System.out.println("##### listener 주문정보받음 : " + 결제승인됨.toJson());
|
||||
|
||||
주문관리 주문 = new 주문관리();
|
||||
주문.setId(결제승인됨.getOrderId())
|
||||
주문관리Repository.save(주문);
|
||||
}
|
||||
}
|
||||
@StreamListener(KafkaProcessor.INPUT)
|
||||
|
||||
Reference in New Issue
Block a user