From 208af8e68cfe762b4ccf8fdde375a2691b3b451d Mon Sep 17 00:00:00 2001 From: jyjang Date: Sun, 19 Apr 2020 16:23:51 +0900 Subject: [PATCH] =?UTF-8?q?=EC=97=85=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 121 ++++++++++++++---- .../main/java/fooddelivery/PolicyHandler.java | 6 + 2 files changed, 104 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index acba770..45c89ef 100644 --- a/README.md +++ b/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 # 모든 주문의 상태가 "배송됨"으로 확인 ``` diff --git a/store/src/main/java/fooddelivery/PolicyHandler.java b/store/src/main/java/fooddelivery/PolicyHandler.java index e00b4d5..4ca2125 100755 --- a/store/src/main/java/fooddelivery/PolicyHandler.java +++ b/store/src/main/java/fooddelivery/PolicyHandler.java @@ -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)