본 예제는 MSA/DDD/Event Storming/EDA 를 포괄하는 분석/설계/구현/운영 전단계를 커버하도록 구성한 예제입니다. 이는 클라우드 네이티브 애플리케이션의 개발에 요구되는 체크포인트들을 통과하기 위한 예시 답안을 포함합니다.
체크포인트 : https://workflowy.com/s/assessment-check-po/T5YrzcMewfo4J6LW
- 서비스 시나리오
- 체크포인트
- 분석/설계
- 구현
- 운영
- 서점 점주가 책을 등록/수정/삭제한다.
- 구매자가 책을 선택하여 구매한다.
- 주문과 동시에 결제가 진행된다.
- 결제가 되면 구매 내역 (Message)이 전달된다.
- 판매자가 주문을 확인하여 배송을 시작한다.
- 고객이 주문을 취소할 수 있다.
- 주문이 취소되면 배송이 취소된다.
- 주문이 취소될 경우 취소 내역 (Message)이 전달된다.
- 배송상태가 바뀔 때 마다 알림을 보낸다
-
트랜잭션
- 결제가 되지 않은 주문건은 거래가 성립되지 성립되지 않아야 한다. (Sync 호출)
-
장애격리
- 상점관리 기능이 수행되지 않더라도 주문은 365일 24시간 받을 수 있어야 한다. [Async (event-driven), Eventual Consistency]
- 결제시스템이 과중되면 사용자를 잠시동안 받지 않고 결제를 잠시후에 하도록 유도한다. [Circuit breaker, fallback]
-
성능
- 구매자가 상점관리에서 확인할 수 있는 구매 정보 및 배송상태 등을 주문시스템에서 한번에 확인할 수 있어야 한다 [CQRS]
- 배송상태가 상태가 바뀔 때마다 메시지로 알림을 줄 수 있어야 한다 [Event driven]
-
분석 설계
-
이벤트스토밍:
- 스티커 색상별 객체의 의미를 제대로 이해하여 헥사고날 아키텍처와의 연계 설계에 적절히 반영하고 있는가?
- 각 도메인 이벤트가 의미있는 수준으로 정의되었는가?
- 어그리게잇: Command와 Event 들을 ACID 트랜잭션 단위의 Aggregate 로 제대로 묶었는가?
- 기능적 요구사항과 비기능적 요구사항을 누락 없이 반영하였는가?
-
서브 도메인, 바운디드 컨텍스트 분리
- 팀별 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 등으로 증명
- Contract Test : 자동화된 경계 테스트를 통하여 구현 오류나 API 계약위반를 미리 차단 가능한가?
-
MSAEz 로 모델링한 이벤트 스토밍 결과: https://labs.msaez.io/#/storming/RWvnPeZmdwhH2VUyVhPmZ5pZgLw2/4e8fde73cfb97272d06117d1e84f642d
과정중 도출된 잘못된 도메인 이벤트들을 걸러내는 작업을 수행함 주문시>주문전달완료, 주문시>책선택완료 : UI 의 이벤트이지, 업무적인 의미의 이벤트가 아니라서 제외
front의 Order, store 의 주문처리, payment 의 결제이력, delivery의 배송현황은 그와 연결된 command 와 event 들에 의하여 트랜잭션이 유지되어야 하는 단위로 그들 끼리 묶어줌
도메인 서열 분리
- Core Domain: app(front), store : 없어서는 안될 핵심 서비스이며, 연간 Up-time SLA 수준을 99.999% 목표, 배포주기는 app 의 경우 1주일 1회 미만, store 의 경우 1개월 1회 미만
- Supporting Domain: marketing, customer : 경쟁력을 내기위한 서비스이며, SLA 수준은 연간 60% 이상 uptime 목표, 배포주기는 각 팀의 자율이나 표준 스프린트 주기가 1주일 이므로 1주일 1회 이상을 기준으로 함.
- General Domain: pay : 결제서비스로 3rd Party 외부 서비스를 사용하는 것이 경쟁력이 높음
View Model 및 폴리시의 이동과 컨텍스트 매핑 추가
기능적 요구사항
- 서점 점주가 책을 등록/수정/삭제한다. (ok)
- 구매자가 책을 선택하여 구매한다. (ok)
- 주문과 동시에 결제가 진행된다. (ok)
- 결제가 되면 구매 내역 (Message)이 전달된다. (not yet)
- 판매자가 주문을 확인하여 배송을 시작한다. (ok)
- 고객이 주문을 취소할 수 있다. (ok)
- 주문이 취소되면 배송이 취소된다. (not yet)
- 주문이 취소될 경우 취소 내역 (Message)이 전달된다. (not yet)
- 배송상태가 바뀔 때 마다 알림을 보낸다. (not yet)
수정된 모델은 모든 요구사항을 커버함
마이크로 서비스를 넘나드는 시나리오에 대한 트랜잭션 처리
- 고객 주문시 결제처리: 결제가 완료되지 않은 주문은 미처리됨에 따라, ACID 트랜잭션 적용. 주문완료시 결제처리에 대해서는 Request-Response 방식 처리.
- 결제 완료시 점주연결 및 배송처리: App(front) 에서 Store 마이크로서비스로 주문요청이 전달되는 과정에 있어서 Store 마이크로 서비스가 별도의 배포주기를 가지기 때문에 Eventual > Consistency 방식으로 트랜잭션 처리함.
- 나머지 모든 inter-microservice 트랜잭션: 주문상태, 배달상태 등 모든 이벤트에 대해 카톡을 처리하는 등, 데이터 일관성의 시점이 크리티컬하지 않은 모든 경우가 대부분이라 판단, Eventual Consistency 를 기본으로 채택함.
- Chris Richardson, MSA Patterns 참고하여 Inbound adaptor와 Outbound adaptor를 구분함
- 호출관계에서 PubSub 과 Req/Resp 를 구분함
- 서브 도메인과 바운디드 컨텍스트의 분리: 각 팀의 KPI 별로 아래와 같이 관심 구현 스토리를 나눠가짐
분석/설계 단계에서 도출된 헥사고날 아키텍처에 따라, 각 BC별로 대변되는 마이크로 서비스들을 스프링부트와 자바로 구현하였다. 구현한 각 서비스를 로컬에서 실행하는 방법은 아래와 같다 (각자의 포트넘버는 8081 ~ 808n 이다)
cd app
mvn spring-boot:run
cd pay
mvn spring-boot:run
cd store
mvn spring-boot:run
cd delivery
mvn spring-boot:run
-
order 서비스의 이벤트 Publish http localhost:8081/orders productId=1 productName="MSA Book" qty=3
-
OrderApplication에서 Run 실행 후 kafka Consumer에서 이벤트 확인
주문상태와 배송상태 등 총 Status에 대해서 확인 할 수 있도록 CQRS로 구현하였다. 비동기식으로 처리되어 발행된 이벤트 기반 Kafka를 통해 수신/처리 되어 별도 OrderStatus table에서 관리한다.
- OrderStatus Entity
@Entity
public class OrderStatus {
// get from order event
@Id
private Long orderId;
private Long bookId;
private Integer qty;
private String bookName;
private String orderStatus;
// get from delivery event
private Long deliveryId;
private String deliveryStatus;
}
- OrderView 서비스의 PolicyHandler를 통해 구현 OrderPlaced, DeliveryStarted, OrderCancelled, DeliveryCancelled 이벤트 발생시, Pub/Sub 기반으로 별도 OrderStatus 테이블에 저장
- OrderStatus 조회시 주문상태/배달상태 등의 정보를 종합적으로 확인
<책 주문>
<책 주문취소, 배달취소>
BookStore 프로젝트에서는 PolicyHandler에서 처리 시 어떤 건에 대한 처리인지를 구별하기 위한 Correlation-key 구현을 이벤트 클래스 안의 변수로 전달받아 서비스간 연관된 처리를 정확하게 구현
- 주문(Order)을 하면 동시에 배송(Delivery) 서비스 상태가 변경됨
- 주문취소를 수행하면 다시 배송(Delivery) 서비스 상태가 변경됨
- 주문 생성
http localhost:8081/orders productId=1 quantity=3 customerId="[email protected]" customerName="Lee" customerAddr="seoul"
- 배송 확인
http localhost:8082/deliveries
- 부하 툴을 사용하여 주문 생성
siege -c2 -t10S -v --content-type "application/json" 'http://localhost:8081/orders POST {"productId":2, "quantity":1}
- fallback 처리를 하여 유연하게 대처
- Spring Gateway 서비스를 추가후 application.yaml 내에 각 마이크로서비스의 routes를 추가함
server:
port: 8088
---
spring:
profiles: default
cloud:
gateway:
routes:
- id: front
uri: http://localhost:8081
predicates:
- Path=/orders/**, /payments/**
- id: store
uri: http://localhost:8082
predicates:
- Path=/storeOrders/**, /menus/** /topBooks/**
- id: delivery
uri: http://localhost:8083
predicates:
- Path=/deliveries/**
- id: frontend
uri: http://localhost:8080
predicates:
- Path=/**
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins:
- "*"
allowedMethods:
- "*"
allowedHeaders:
- "*"
allowCredentials: true
- AWS CodeBuild 를 통한 store, delivery 서비스 배포
- 컨터이너 리소스 CPU 200m 설정
- HPA(Horizonal Pod Autoscale)를 cpu임계치=20%, 최대Pod수=3, 최저Pod수=1 로 설정
kubectl autoscale deployment team4-store --cpu-percent=20 --min=1 --max=3
- siege 컨테이너 배포 후 siege 컨테이너 내부에서 동시사용자 1명, 20초간 부하 테스트를 실행
kubectl apply -f - <<EOF
apiVersion: v1
kind: Pod
metadata:
name: siege
spec:
containers:
- name: siege
image: apexacme/siege-nginx
EOF
kubectl exec -it siege -- /bin/bash
siege -c1 -t10S -v http://team4-store:8080/profile/products
- CPU 리소스가 20%를 넘어서자 HPA 이벤트가 발생하고 Replicas=3 으로 AutoScale 실행
- 쿨타임 이후 다시 3->1 로 Replica Set 변경
- Liveness Probe 설정 후 재배포 실행
livenessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 120
timeoutSeconds: 2
periodSeconds: 5
failureThreshold: 5
- Spring-Boot 설정문제로 재배포 된 컨테이너 내부 오류 발생
- Liveness Probe 실패로 컨테이너 재시작 이벤트 발생
- HPA 제거
kubectl delete hpa team4-store
- Readiness Probe 미설정 배포 시 siege 테스트 결과가 Availability 69.94% 임을 확인
- Readiness Probe 설정 후 재배포 실행
readinessProbe:
httpGet:
path: /actuator/health
port: 8080
initialDelaySeconds: 10
timeoutSeconds: 2
periodSeconds: 5
failureThreshold: 10
- Readiness Probe 설정 이후 배포 시 siege 테스트 결과가 Availability 100% 임을 확인