기존 조직 구조
기존에는 개발(dev)과 운영팀(ops)이 분리되어 있었지만,
민첩하고 빠른 서비스 개발과 운영을 위해 개발과 운영을 분리하지 않고 한팀으로 devops 로 통합 운영한다.
요구사항 도출부터 설계,개발, 운영까지 하나의 흐름으로 연결되도록 bizdevops로 업무와 IT의 벽이 허물어진 체계를 지향한다.
[기획(Biz)] → [개발(Dev)] → [배포/운영(Ops)] → [피드백] → 반복
에자일로 빠르게 개발하기 위해서는
비즈니스 영역을 잘 알아야하고,
도메인 중심으로 소프트웨어를 설계하고,
설계할 때는 이벤트스토밍으로 쉽게 풀어간다.
계획 다 짜놓고 오래 개발하지 말고
조금씩 만들고 빨리 테스트하면서 고쳐 나간다.
=> 고객 가치를 빠르게 전달하는 개발 방법론
마이크로서비스 == 클라우드 네이티브 애플리케이션
애자일을 실현하기 위한 기술적 환경, 즉 ‘어디서 어떻게 개발·배포할 것인가’에 대한 방식
CSP (Cloud Service Provider, 예: AWS, Azure, GCP)가 인프라를 관리하지만, 네이티브로 잘하는, 최적화된 이라는 의미를 담는다.
클라우드 인프라에 최적화된 방식으로 앱을 만들고, 서비스가 요청이 많아지면 자동으로 서비스를 늘려준다.
✔ 동기 호출이 일반적
내부 메서드 간 호출이므로 즉시 응답을 기다리는 구조 (동기적)
✔ 강한 결합 (Tight Coupling)
모든 컴포넌트가 하나의 애플리케이션 안에 tightly 연결되어 있음
하나가 바뀌면 다른 것도 영향을 받음
✔ 공유된 통합 DB 사용
하나의 중앙 DB를 모든 모듈이 함께 사용
기능 단위(도메인 단위)로 작은 서비스들로 나누어서 개발하고 배포한다.
각 서비스는 독립적인 DB와 배포 주기를 가진다.
서로 REST API와 메시지 큐로 통신한다.
🔁 동기/비동기 호출 모두 가능
마이크로서비스 간 통신은 보통 다음 둘 중 하나로 함:
- ✅ 동기 호출 (REST API): 요청 → 응답 즉시 받음
- ✅ 비동기 호출 (Kafka, RabbitMQ 등): 메시지를 보내고 응답을 기다리지 않음
→ 느슨한 결합(Loosely Coupled) 가능
❌ 공유 DB 사용 X (원칙적으로 금지)
각 서비스는 자신의 DB만 사용해야 함
다른 서비스의 DB에 직접 접근하지 않고, API나 메시지로 요청
Domain : 비즈니스 문제 영역(주문, 결제 등)
바운디드 컨텍스트(Bounded Context)
: 의미가 일관된 도메인의 경계
같은 단어라도 맥락에 따라 의미가 달라서 모델을 하나로 통합하면 문제가 된다.
하나의 컨텍스트 내부에서는 용어와 모델의 의미가 명확해야 한다.
바운디드 컨텍스트 | 주요 모델 | 핵심 책임 |
---|---|---|
주문(Order BC) | 주문, 상품, 수량 | 상품 주문, 상태 관리 |
결제(Payment BC) | 결제정보, 카드 | 결제 처리, 환불 등 |
배송(Shipping BC) | 송장, 주소 | 배송 출발, 도착 확인 등 |
고객(CRM BC) | 고객, 주소록 | 고객정보, 연락처 |
복잡한 비즈니스 요구사항을 잘 반영하기 위해, 도메인을 중심으로 소프트웨어를 설계하는 방법론
=> 개발자와 기획자가 같은 언어로 도메인을 잘게 나눠서 이해하고 설계
어떤 관점으로 도메인을 쪼갤지는 비즈니스 관점과 기술 관점에 따라 달라진다.
사용자를 기준으로, 인프라를 기준으로, 쪼개진 하위 도메인을 세 가지 유형으로 분류할 수 있다.
✅ Core 도메인은 내부에서 집중적으로 설계해야 하며,
Supportive와 Generic은 외부 솔루션이나 오픈소스 도입으로 대체 가능.
애자일은 빠르게 변화하는 요구사항을 민첩하게 대응하고, 고객 중심의 가치를 빠르게 제공하기 위한 개발 방법론이다. 이를 실현하기 위해서 는 3가지의 핵심 아키텍쳐/프로세스를 가진다.
서비스 간에 직접 통신하지 않고, pubsub이 이벤트라고 하는 제3의 공유 공간에 남기고 필요한 서비스가 구독(Subscribe)해서 반응하는 방식
용어 | 의미 |
---|---|
Publisher (발행자) | 이벤트를 생성해서 공유지대(PubSub)에 올림 (예: 주문 서비스) |
Subscriber (구독자) | 그 이벤트를 듣고 필요한 일을 함 (예: 결제 서비스, 배송 서비스) |
Pub/Sub 시스템 | Kafka, RabbitMQ 같은 중간 전달자(이벤트 저장소) |
[Biz: 요구사항 정의]
↓
[Dev: 기능 설계 및 개발]
↓
[Ops: 배포 및 모니터링]
↑
[지속적 피드백 & 개선]
DDD는 복잡한 비즈니스 요구를 반영한 소프트웨어를 만들기 위한 설계 철학과 실천 기법
비즈니스 도메인에 집중해서 모델 중심의 설계를 지향한다.
설계는 개발자와 도메인 전문가가 공통의 언어를 기반으로 협업한다.
자가치유적 구조화
코어core와 지원support 부분으로 나눈다.
core : 핵심적, 잦은 변경 필요
support : 배포주기 잦지 않은 영역, 일부 사용자 한정
Business capability단위로 나눈다.
도메인에서 일어나는 이벤드(사건) 을 중심으로 모델링한다.
서비스에 어떤일이 일어나는지를 포스트잇 붙이듯 정리하면서 모델링하는 방법
=> DDD로 모델링하는 방법이 이벤트스토밍이다.
복잡한 도메인을 시각적으로 탐색하고 모델링하는 기법
도메인 주도 설계(DDD)의 핵심 기법 중 하나로,
포스트잇을 이용해 도메인 안에서 발생하는 이벤트를 중심으로 흐름을 그려가며
모든 이해관계자(개발자, 기획자, 디자이너 등)가 함께 소통하고 도메인을 설계합니다.
구성 요소 | 설명 |
---|---|
Domain Event | 도메인에서 실제로 일어난 사건. 시스템의 상태를 변화시킨 사실(Fact) 또는 결과 (예: ‘주문이 생성되었다’) 📌 과거형(PP형) 으로 작성 |
Command | 사용자의 의도나 요청을 나타내며, 시스템이 처리해야 할 행동 (예: ‘주문을 생성한다’) 📌 현재형 으로 작성 |
Actor | Command를 발생시키는 주체, 즉 사용자나 외부 시스템 |
Aggregate | 관련된 데이터와 로직이 모인 덩어리로, 여러 Entity, Value Object로 구성된 도메인의 일관성 단위 |
Policy | 특정 이벤트가 발생했을 때 자동으로 실행되는 비즈니스 규칙이나 처리 로직 (예: '주문이 생성되면 결제 요청') Event에 대한 반응 |
Read Model | 사용자에게 보여지는 데이터, 또는 Command 실행에 필요한 데이터를 효율적으로 제공하기 위한 모델 (CQRS 기반) |
Domain Event : 발생한 사실,결과, OUTPUT - pp형으로 작성,행동에 따른 결과
Command : 의사결정, INPUT, API, UI 버튼 - 현재형으로 작성,행동
Actor : 사용자
Aggregate : 구현제 덩어리, 시스템, 모듈 - 하나 이상의 엔터티, value objects 의 집합체
Policy : 정책, 반응 - Event에 대한 반응
Read Model : Command에 필요한 자료 - 유저가 참고하는 데이터, CQRS으로 수집
🔁 이벤트스토밍 절차 요약
도메인 이벤트 발굴 (무엇이 일어났는가?)
커맨드로 인한 인과 관계 정리 (무엇을 했기에?)
정책에 따라 처리 방식 정의 (어떻게 반응할 것인가?)
에그리게이트로 상태를 관리 (무엇이 바뀌는가?)
리드모델/조회모델 정의 (사용자에게 어떤 정보를 보여줄까?)
바운디드 컨텍스트로 경계 나누기 (어떤 책임을 어느 팀이 가질까?)
"Event는 domain 전문자(해당 팀)이 책임진다."
도메인 이벤트는 무엇이 일어났는가 를 나타내고, 해당 도메인을 잘 아는 팀이 작성한다.
예약이 완료되었다
는 예약관리팀의 책임!!!!
“Policy은 다른 집 자식이다!”
→ 정책은 다른 도메인의 관심사에 반응으로, 다른 팀/컨텍스트가 소유하는 것이 맞음
예약이 완료되면 알림을 보낸다
는 알림팀의 policy!!!!!!!!
Policy는 "자기 자리를 찾아가야 한다"
다음과 같이 수정해야한다.
Policy는 Event에 반응하며 자체적으로 실행되는 "백그라운드 데몬"처럼 작동한다.
상품팀에 재고차감은 배송팀의 도메인 이벤트에 의해서 일어난다.
주문팀은 이벤트를 발행(publish)할 뿐, 누가 이걸 구독(subscribe)할지 알지 못한다.
Publish는 목적지 없이 브로드캐스트
→ OrderCancelled 이벤트는 여러 팀(조리팀, 배달팀 등)이 자율적으로 구독하여 정책 실행
Policy는 이벤트 소비자가 정의하고, publish는 소비자에 대해 몰라도 된다. -> 느슨한 결합
애써 나누어 놓은게 의미가 없어져 버린다.
해당 서비스가 스스로 생각해서 작업을 수행한다.
[Service A] --이벤트 발행--> [Kafka Topic] --구독--> [Service B, C]
시스템을 여러 개의 작고 독립된 서비스로 분리하여 구성
각 서비스는 자체 데이터베이스와 도메인을 갖고 독립 배포됨
서비스 간의 직접 REST API로 통신하는 것이 아니라, 이벤트를 발행(pub)하고 구독(sub)하는 구조
-> 느슨한 결합, 확장성 확보, 장애 격리 가능하다.
이벤트 기반 : 서비스는 필요한 정보를 이벤트를 통해 비동기로 전달한다.
브로드캐스트 방식 : 발행자는 목적지를 모르고 보내고, 수신자는 필요한 것만 듣는다.
시나리오에서 결론을 먼저 가져온다.
=> 도출된 결과를 중심으로 Domain Event를 정의
=> 도메인의 핵심 행동을 동사 pp형으로 표현
command는 사용자의 요청으로 동사 원형으로 표현
aggregate 는 상태 변경과 이벤트 발행 객체
policy는 이벤트에 반응하는 비즈니스 규칙이 된다.
Java 구현 대응
개념 | 설명 | Java 대응 |
---|---|---|
Command | 사용자 요청, API | REST API (@PostMapping , @DeleteMapping ) |
Domain Event | 발생한 결과 | Java 클래스 (POJO, JSON 직렬화) |
Aggregate | 상태 변경 및 이벤트 발행 객체 | 엔티티 + 상태관리 |
Policy | 이벤트에 반응하는 비즈니스 규칙 | 비동기 이벤트 리스너 (@KafkaListener , 데몬 등) |
어그리거트 : Command를 받아 상태를 변경하고 Event를 발행하는 도메인 객체 묶음 (main이 되는 DB)
속성 동기화
버튼으로 부여할 수 있다. 도메인 구분
Command는 사용자의 요청에 해당하며, 시스템의 상태 변경을 유발한다.
JAVA 기반 시스템에서 command 처리 중 생명주기 이벤트 (Lifecycle Hook) 으로 도메인 이벤트를 발생시키거나 후속처리를 트리거할 수 있다.
생명주기 | 실행 시점 | 활용 예 |
---|---|---|
@PrePersist | 저장되기 직전 | 유효성 검사, 로깅, 생성 준비 |
@PostPersist | 저장된 직후 | 이벤트 발행 OrderPlaced |
@PreUpdate | 변경되기 직전 | 상태 체크, 변경 조건 확인 |
@PostUpdate | 변경된 직후 | 이벤트 발행 InventoryDecreased , DeliveryCompleted |
@PreRemove | 삭제되기 직전 | 삭제 전 이벤트 발행 OrderCancelled |
@PostRemove | 삭제된 직후 | 로그 기록, 자원 정리 |
command는 POST를 기본값으로 가진다.
주문하기 command는 POST로 메서드를 지정한다.
취소하기 command는 DELETE 메서드를 지정한다.
영속화되는 시점에 해당되는 트리거는
구분 | 특징 | 사용 예 |
---|---|---|
Pre 계열 | 트랜잭션 중 실행됨, 예외 시 롤백 가능 | @PrePersist , @PreRemove |
Post 계열 | 트랜잭션 이후 실행됨, DB 반영 완료 후 | @PostPersist , @PostUpdate |
Command와 Query의 책임을 분리하여 처리하는 아키텍처 패턴
Command: 시스템의 상태 변경을 유발하는 동작 (쓰기)
Query: 시스템의 상태를 조회하는 동작 (읽기)
쓰기와 읽기의 모델과 저장소를 분리한다.
마이크로서비스로 나눈 이상 각자의 데이터가 다 다르고, 각 도메인이 서로 다른 DB를 사용한다.
=> 직접적인 join이나 통합 쿼리가 불가능하다.
필요에 의해서 데이터를 모아두고 돌아다니는 도메인 이벤트(Event)를 모아두고 이에 따라 갱신한다.
읽기 전용 모델(Read model)을 따로 구성한다.
이벤트를 중심으로 데이터를 연동/갱신하는 구조를 이벤트 드리븐 아키텍쳐
또는 CQRS
에서 구현한다.
Event를 기반으로 데이터를 수신하고 Read Model을 객신하는 구조
Query의 유형
Create : OrderPlaced (주문 내역 생성)
Update : Delivery completed (배송 상태 업데이트)
delivery가 complete될 때 Update한다.
- set : 어떻게 바꾸는가
- where : 무엇을 바꾸는가
Update : Order cancelled (주문 상태 취소로 변경
Update : shipping returned (반품 상태로 변경)
기능적 요구사항
- 고객이 메뉴를 선택하고, 선택한 메뉴에 대해 결제함으로써 주문이 발생한다.
- 주문이 되면 입점 상점주에게 주문정보가 전달된다.
- 상점주는 주문을 수락하거나 거절할 수 있다.
- 상점주는 요리 시작시와 완료 시점에 시스템에 상태를 입력한다.
- 고객은 아직 요리가 시작되지 않은 주문을 취소할 수 있다.
- 요리가 시작되면 고객 지역 인근의 라이더들에 배송정보가 전달된다.
- 라이더가 해당 요리를 Pick한 후, 출발전 앱에 등록하면 배송시작 정보가 앱을 통해 고객에게 통보된다.
- 고객은 주문상태를 중간중간 조회한다.
- 시스템은 주문상태가 바뀔 때 마다 카톡으로 알림을 발송한다.
- 라이더가 요리를 전달한뒤 배송확인 버튼을 탭하여, 모든 거래가 완료된다.
(고객)
메뉴선택
메뉴결제
주문발생 : OrderPlaced
결제가 주된 것이 아님. 그저 행위일 뿐
요리가 시작되지 않은 요리를 취소할 수 있다는 로직일 뿐이고, 이는 나태내지 않는다 코드로 구현할 뿐
(상점주)
주문정보 받음
주문수락/거절
요리시작/완료
주문취소
주문 정보는 정보의 흐름이구만 ... 이벤트로써...? 아마 상점주에게만 가지는 정보일듯
수락했다 또는 거절했다의 행동 event => true, false로 분기처리가능
조리시작했다 또는 완료했다는 행동 event => command로 api를 각각나누는게 좋음
(라이더)
배송정보 전달
배송등록
~ 되면, ~ 한다 와 같은 이벤트-반응 규칙을 명시한다.
주문이 되면, 상점주에게 주문정보가 전달된다. (-이면 -이다. policy가 필요하다)
요리가 시작되면, 라이더에게 배송정보가 전달된다. (policy 필요)
픽업이 되면, 사용자에게 주문상태가 전달된다. (policy 필요)
command 유형에 따라 적절한 HTTP Method를 지정
비동기 이벤트 기반으로 조회 전용 모델(read model)을 구성한다. (CQRS 적용)
서비스 간의 직접 요청-응답(REST)를 피하고,
필요한 데이터를 이벤트로 받아 저장한다.
address를 rider가 얻어오는 방법..
store에는 address 정보가 없어서 rider에게 줄 수 없음
policy에 order에서 address 를 가져오도록 해야한다.
read model
Read : Query for Aggregate 로 단순 get를 해서 load delivery info에서 가져온다.
load delivery info -> getAddress(read model) requset와 response의 관계로 이어지게 된다. 서비스가 중간에 죽으면 가져올 수 없게 되어 문제가 발생한다. (res/req를 많이 사용하지 않도록 지양한다.)
Create : orderplaced -> load delivery info
Update : cookstart -> load delivery info
위의 2과정으로 비동기할 수 있다.
📌 Rider가 address를 얻어오려면,
→ OrderPlaced 이벤트에 address 포함 → DeliveryReadModel에 저장됨
온라인에서 코드 실행 가능한 환경이다.
gitpod classic > 원격환경 사용