# MSA/DDD/Event Storming 예제 - 음식배달 ## 서비스 시나리오 ## 체크포인트 https://workflowy.com/s/assessment-check-po/T5YrzcMewfo4J6LW - 분석 설계 - 이벤트스토밍: - 스티커 색상별 객체의 의미를 제대로 이해하여 헥사고날 아키텍처와의 연계 설계에 적절히 반영하고 있는가? - 각 도메인 이벤트가 의미있는 수준으로 정의되었는가? - 어그리게잇: Command와 Event 들을 ACID 트랜잭션 단위의 Aggregate 로 제대로 묶었는가? - View 와 Command 를 구분하였는가? - 기능적 요구사항과 비기능적 요구사항을 누락 없이 반영하였는가? - 서브 도메인, 바운디드 컨텍스트 분리 - 팀별 KPI 와 관심사, 상이한 배포주기 등에 따른  Sub-domain 이나 Bounded Context 를 적절히 분리하였고 그 분리 기준의 합리성이 충분히 설명되는가? - 적어도 3개 이상 서비스 분리 - 폴리글랏 설계: 각 마이크로 서비스들의 구현 목표와 기능 특성에 따른 각자의 기술 Stack 과 저장소 구조를 다양하게 채택하여 설계하였는가? - 서비스 시나리오 중 ACID 트랜잭션이 크리티컬한 Use 케이스에 대하여 무리하게 서비스가 과다하게 조밀히 분리되지 않았는가? - 컨텍스트 매핑 / 이벤트 드리븐 아키텍처 - 업무 중요성과  도메인간 서열을 구분할 수 있는가? (Core, Supporting, General Domain) - Request-Response 방식과 이벤트 드리븐 방식을 구분하여 설계할 수 있는가? - 장애격리: 서포팅 서비스를 제거 하여도 기존 서비스에 영향이 없도록 설계하였는가? - 신규 서비스를 추가 하였을때 기존 서비스의 데이터베이스에 영향이 없도록 설계(열려있는 아키택처)할 수 있는가? - 이벤트와 폴리시를 연결하기 위한 Correlation-key 연결을 제대로 설계하였는가? - 헥사고날 아키텍처 - 설계 결과에 따른 헥사고날 아키텍처 다이어그램을 제대로 그렸는가? - 구현 - [DDD] 분석단계에서의 스티커별 색상과 헥사고날 아키텍처에 따라 구현체가 매핑되게 개발되었는가? - Entity Pattern 과 Repository Pattern 을 적용하여 JPA 를 통하여 데이터 접근 어댑터를 개발하였는가 - [헥사고날 아키텍처] REST Inbound adaptor 이외에 gRPC 등의 Inbound Adaptor 를 추가함에 있어서 도메인 모델의 손상을 주지 않고 새로운 프로토콜에 기존 구현체를 적응시킬 수 있는가? - 분석단계에서의 유비쿼터스 랭귀지 (업무현장에서 쓰는 용어) 를 사용하여 소스코드가 서술되었는가? - Request-Response 방식의 서비스 중심 아키텍처 구현 - 마이크로 서비스간 Request-Response 호출에 있어 대상 서비스를 어떠한 방식으로 찾아서 호출 하였는가? (Service Discovery, REST, FeignClient) - 서킷브레이커를 통하여  장애를 격리시킬 수 있는가? - 이벤트 드리븐 아키텍처의 구현 - 카프카를 이용하여 PubSub 으로 하나 이상의 서비스가 연동되었는가? - Correlation-key: 각 이벤트 건 (메시지)가 어떠한 폴리시를 처리할때 어떤 건에 연결된 처리건인지를 구별하기 위한 Correlation-key 연결을 제대로 구현 하였는가? - Message Consumer 마이크로서비스가 장애상황에서 수신받지 못했던 기존 이벤트들을 다시 수신받아 처리하는가? - Scaling-out: Message Consumer 마이크로서비스의 Replica 를 추가했을때 중복없이 이벤트를 수신할 수 있는가 - CQRS: Materialized View 를 구현하여, 타 마이크로서비스의 데이터 원본에 접근없이(Composite 서비스나 조인SQL 등 없이) 도 내 서비스의 화면 구성과 잦은 조회가 가능한가? - 폴리글랏 플로그래밍 - 각 마이크로 서비스들이 하나이상의 각자의 기술 Stack 으로 구성되었는가? - 각 마이크로 서비스들이 각자의 저장소 구조를 자율적으로 채택하고 각자의 저장소 유형 (RDB, NoSQL, File System 등)을 선택하여 구현하였는가? - API 게이트웨이 - API GW를 통하여 마이크로 서비스들의 집입점을 통일할 수 있는가? - 게이트웨이와 인증서버(OAuth), JWT 토큰 인증을 통하여 마이크로서비스들을 보호할 수 있는가? - 운영 - SLA 준수 - 셀프힐링: Liveness Probe 를 통하여 어떠한 서비스의 health 상태가 지속적으로 저하됨에 따라 어떠한 임계치에서 pod 가 재생되는 것을 증명할 수 있는가? - 서킷브레이커, 레이트리밋 등을 통한 장애격리와 성능효율을 높힐 수 있는가? - 오토스케일러 (HPA) 를 설정하여 확장적 운영이 가능한가? - 모니터링, 앨럿팅: - 무정지 운영 CI/CD (10) - Readiness Probe 의 설정과 Rolling update을 통하여 신규 버전이 완전히 서비스를 받을 수 있는 상태일때 신규버전의 서비스로 전환됨을 siege 등으로 증명 (3) - Contract Test : 자동화된 경계 테스트를 통하여 구현 오류나 API 계약위반를 미리 차단 가능한가? (5) ## 이벤트 스토밍 결과: http://msaez.io/#/storming/nZJ2QhwVc4NlVJPbtTkZ8x9jclF2/every/a77281d704710b0c2e6a823b6e6d973a/-M5AV2z--su_i4BfQfeF ### 과정 소개: - - - ## 구현: ### 폴리글랏 퍼시스턴스 / 플랫폼 ``` cd 주문 mvn spring-boot:run # H2 cd 결제 mvn spring-boot:run # H2 cd 상점 mvn spring-boot:run # MySQL cd 마케팅 python marketing # 파이썬 ``` ### 비동기식 호출 / 시간적 디커플링 / 장애격리 / 최종 (Eventual) 일관성 테스트 ``` #상점 서비스를 잠시 내려놓음 kill (lsof -i ) #주문처리 http localhost:8080/주문s 품목=통닭 수량=1 주소=서울 http localhost:8080/주문s 품목=피자 수량=2 주소=서울 #주문상태 확인 http localhost:8080/주문s # 주문상태 안바뀜 확인 #상점 서비스 기동 cd 상점 mvn spring-boot:run #주문상태 확인 http localhost:8080/주문s # 모든 주문의 상태가 배송됨으로 확인 ``` ### 동기식 호출 / 타임 커플링 / 결제 안되면 주문도 안됨 ``` # 결제 서비스를 잠시 내려놓음 kill (lsof -i) #주문처리 http localhost:8080/주문s 품목=통닭 수량=1 주소=서울 #오류 http localhost:8080/주문s 품목=피자 수량=2 주소=서울 #오류 #결제서비스 재기동 cd 결제 mvn spring-boot:run #주문처리 http localhost:8080/주문s 품목=통닭 수량=1 주소=서울 #성공 http localhost:8080/주문s 품목=피자 수량=2 주소=서울 #성공 ``` ## 운영 ### 동기식 호출 / 서킷 브레이킹 / 장애격 * 서킷 브레이킹 프레임워크의 선택: Spring FeignClient + Hystrix 옵션을 사용하여 구현함 시나리오는 단말앱(app)-->결제(pay) 시의 연결을 RESTful Request/Response 로 연동하여 요청이 쇄도할 경우 CB 를 통하여 장애격리하는 시나리오. - 결제서비스를 호출하기 위하여 Stub과 (FeignClient) 를 이용하여 Service 대행 인터페이스 (Proxy) 를 구현 ``` # (app) 결제이력Service.java package fooddelivery.external; @FeignClient(name="pay", url="http://localhost:8082")//, fallback = 결제이력ServiceFallback.class) public interface 결제이력Service { @RequestMapping(method= RequestMethod.POST, path="/결제이력s") public void 결제(@RequestBody 결제이력 pay); } ``` - 주문을 받은 직후(@PostPersist) 결제를 요청하도록 처리 ``` # Order.java (Entity) @PostPersist public void onPostPersist(){ fooddelivery.external.결제이력 pay = new fooddelivery.external.결제이력(); pay.setOrderId(getOrderId()); Application.applicationContext.getBean(fooddelivery.external.결제이력Service.class) .결제(pay); } ``` - Hystrix 를 설정: 요청처리 쓰레드에서 처리시간이 610 밀리가 넘어서기 시작하여 어느정도 유지되면 CB 회로가 닫히도록 (요청을 빠르게 실패처리, 차단) 설정 ``` # application.yml hystrix: command: # 전역설정 default: execution.isolation.thread.timeoutInMilliseconds: 610 ``` - 피호출 서비스(결제:pay) 의 임의 부하 처리 - 400 밀리에서 증감 220 밀리 정도 왔다갔다 하게 ``` # (pay) 결제이력.java (Entity) @PrePersist public void onPrePersist(){ //결제이력을 저장한 후 적당한 시간 끌기 ... try { Thread.currentThread().sleep((long) (400 + Math.random() * 220)); } catch (InterruptedException e) { e.printStackTrace(); } } ``` * 부하테스터 siege 툴을 통한 서킷 브레이커 동작 확인: - 동시사용자 100명 - 60초 동안 실시 ``` $ siege -c100 -t60S -r10 --content-type "application/json" 'http://localhost:8081/orders POST {"item": "chicken"}' ** SIEGE 4.0.5 ** Preparing 100 concurrent users for battle. The server is now under siege... HTTP/1.1 201 0.68 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 0.68 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 0.70 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 0.70 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 0.73 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 0.75 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 0.77 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 0.97 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 0.81 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 0.87 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 1.12 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 1.16 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 1.17 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 1.26 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 1.25 secs: 207 bytes ==> POST http://localhost:8081/orders * 요청이 과도하여 CB를 동작함 요청을 차단 HTTP/1.1 500 1.29 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 1.24 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 1.23 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 1.42 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 2.08 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 1.29 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 1.24 secs: 248 bytes ==> POST http://localhost:8081/orders * 요청을 어느정도 돌려보내고나니, 기존에 밀린 일들이 처리되었고, 회로를 닫아 요청을 다시 받기 시작 HTTP/1.1 201 1.46 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 1.33 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 1.36 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 1.63 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 1.65 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 1.68 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 1.69 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 1.71 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 1.71 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 1.74 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 1.76 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 1.79 secs: 207 bytes ==> POST http://localhost:8081/orders * 다시 요청이 쌓이기 시작하여 건당 처리시간이 610 밀리를 살짝 넘기기 시작 => 회로 열기 => 요청 실패처리 HTTP/1.1 500 1.93 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 1.92 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 1.93 secs: 248 bytes ==> POST http://localhost:8081/orders * 생각보다 빨리 상태 호전됨 - (건당 (쓰레드당) 처리시간이 610 밀리 미만으로 회복) => 요청 수락 HTTP/1.1 201 2.24 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 2.32 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 2.16 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 2.19 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 2.19 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 2.19 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 2.21 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 2.29 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 2.30 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 2.38 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 2.59 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 2.61 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 2.62 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 2.64 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.01 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.27 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.33 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.45 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.52 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.57 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.69 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.70 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.69 secs: 207 bytes ==> POST http://localhost:8081/orders * 이후 이러한 패턴이 계속 반복되면서 시스템은 도미노 현상이나 자원 소모의 폭주 없이 잘 운영됨 HTTP/1.1 500 4.76 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 4.23 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.76 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.74 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 4.82 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.82 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.84 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.66 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 5.03 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 4.22 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 4.19 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 4.18 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.69 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.65 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 5.13 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 4.84 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 4.25 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 4.25 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.80 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 4.87 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 4.33 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.86 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 4.96 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 4.34 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 500 4.04 secs: 248 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.50 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.95 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.54 secs: 207 bytes ==> POST http://localhost:8081/orders HTTP/1.1 201 4.65 secs: 207 bytes ==> POST http://localhost:8081/orders : : Transactions: 1025 hits Availability: 63.55 % Elapsed time: 59.78 secs Data transferred: 0.34 MB Response time: 5.60 secs Transaction rate: 17.15 trans/sec Throughput: 0.01 MB/sec Concurrency: 96.02 Successful transactions: 1025 Failed transactions: 588 Longest transaction: 9.20 Shortest transaction: 0.00 ``` - 63.55% 가 성공하였고, 운영시스템은 죽지 않고 지속적으로 CB 에 의하여 적절히 회로가 열림과 닫힘이 벌어지면서 자원을 보호하고 있음을 보여줌. 물론, 46% 의 실패율은 좋지 않기 때문에 동적 Scale out (replica의 자동적 추가,HPA) 을 통하여 시스템을 확장 해주는 후속처리가 필요. ### 무정지 재배포 ``` ``` ### ```