Building Microservices
architecture
- Building Microservices
Building Microservices
Ch01. 마이크로서비스란?
TL;DR
- 마이크로서비스 아키텍처는 복잡성이 높은 반면에, 독립적 배포, 도메인 중심 모델링, 견고성 및 확장성, 팀 구성 등에 높은 유연성을 부여할 수 있다.
- 조직에서 어느정도의 복잡성을 수용할 것인지 결정해야 한다.
- 마이크로서비스 아키텍처를 채택했다면, 로그 집계와 분산 추적, 컨테이너, 스트리밍, 클라우드 서비스를 고려해야 한다.
Key Ideas
- 마이크로서비스의 장점: 기술 이질성, 견고성, 확장성, 배포 용이성, 조직적 정렬, 조합성
- 마이크로서비스의 고충: 개발자 경험, 기술 과다, 비용, 리포팅, 모니터링과 디버깅, 보안, 테스팅, 지연 시간, 데이터 일관성
- 견고성: 견고성을 향상 시키는 핵심 개념은 벌크헤드다. 고장이 연속적으로 발생하지 않게 문제를 격리하는 것이다.
Open Questions
- 어느 정도의 규모가 되어야 마이크로서비스 아키텍처를 수용할 수 있을까?
- 5명으로 구성된 스타트업에서는 마이크로서비스 아키텍처는 걸림돌이 될 우려가 크다.
- 100명 규모로 빠르게 성장하는 회사라면, 마이크로서비스 아키텍처는 성장을 수용하기 쉬울 것이다.
- 하지만, 사람 수가 아니라 복잡성에 달려있다.
- 하나의 코드베이스로 더 이상 모든 기능을 안전하게 수정할 수 없을 때
- 다른 팀의 배포를 기다려야 하는 상황이 잦을 때
- 팀이 기능 중심이 아니라 도메인 중심으로 나눠질 수 있을 때
- 일부 서비스만 다른 언어/DB/플랫폼이 더 적합할 때
- 트래픽/자원 사용 패턴이 도메인 별로 달라질 때
- 규모가 적어도 마이크로서비스가 유용한 상황이 있을까?
- 도메인 독립 실험이 필요한 경우
- 기술 격리가 중요한 경우
- 팀 구조가 분산되어 있는 경우
- API 게이트웨이/서드파티 연동이 주된 비즈니스인 경우
Ch02. 마이크로서비스 모델링 방법
TL;DR
- 도메인을 자세히 이해하는 것은 낮은 결합도와 강한 응집력, 문제 영역에서의 경계를 찾는 데 도움이 된다.
- 올바른 마이크로서비스의 경계를 만드는 것은 정보 은닉, 높은 응집력, 낮은 결합도이다.
- 하나의 애그리거트는 하나의 마이크로서비스에서 관리돼야 한다.
Key Ideas
- 정보 은닉(Information Hiding): 모듈 경계 뒤에 가능한 많은 세부 정보를 숨기려는 욕구
- 도메인 결합(Domain Coupling): 한 마이크로서비스가 다른 마이크로서비스와 상호작용
- 통과 결합(Pass-Through Coupling): 한 마이크로서비스가 다른 마이크로서비스에 데이터를 전달. 이 경우, 하위 서비스와 직접 통신
- 공통 결합(Common Coupling): 둘 이상의 마이크로서비스가 공통 데이터 집합을 사용. 이 경우, 한 서비스가 온전히 책임을 지도록 해야함
- 내용 결합(Content Coupling): 상위 서비스가 하위 서비스 내부까지 도달해 서비스 내부 상태를 변경. 이 경우, 직접 요청을 보내도록 해야함
Open Questions
- 모든 결합은 나쁜가?
- 결합은 필연적이고, 불필요한 결합을 줄이고 필요한 결합을 느슨하게 유지하는 것이다.
- 좋은 결합의 조건
- 비즈니스적으로 필수
- 안정적인 컨트랙트위에서 이뤄짐
- 시간적 독립성 보장
- 나쁜 결합의 신호
- 다른 서비스의 내부 스키마/컬럼에 의존
- 공통 DB 쓰기로 배포 독립성 상실
- 동기 체인으로 SLO가 전파됨
- 내용 결합은 주로 어떤 상황에서 발생하는가?
- 공유 DB 및 스키마를 여러 서비스가 직접 조회 및 갱신할 때
- 다른 서비스의 내부 엔터티를 DTO처럼 재사용할 때
- GraphQL/REST 응답을 내부 모델 그대로 노출해 외부가 내부 필드에 묶일 때 -> 외부에는 전용 DTO만 노출
- 빠르게 붙이자는 이유로 ACL 없이 타 서비스의 내부 API를 그대로 소비할 때 -> 외부, 내부 도메인 변환 계층
Ch03. 모놀리스 분해
TL;DR
- 마이그레이션은 점진적이어야 한다. 변경하고 나서 해당 변경 사항을 출시하고, 평가하고, 다시 수행한다.
- 모놀리스를 분해할 때에는 여러 패턴이 도움된다. ex) Strangler Fig, Parallel Run, Feature Toggle, …
- 데이터베이스의 성능, 데이터 무결성, 트랜잭션, 변경 도구, 리포팅에 대한 고려가 필요하다.
Key Ideas
- 교살자 무화과 패턴(strangler fig): 새로운 서비스가 만들어졌다면, 기존 시스템에 대한 호출을 가로채고 새 서비스 호출로 리디렉션한다. 만약 변경되지 않았다면, 기존 요청은 그대로 둬도 된다.
- 병렬 실행(parallel run): 모놀리식 기능 구현과 새로운 서비스 구현을 나란히 실행해 같은 요청을 제공하고 결과를 비교한다.
- 기능 토글(feature toggle): 기능을 켜거나 끄고 기능에 대한 2개의 다른 구현 사이를 오가게 하는 메커니즘이다.
- 대상을 선정하고, 분해를 시도하자. 분해는 코드 우선 또는 데이터 우선으로 진행된다.
- 완벽한 선경지명과 무한한 자금만 있으면 가질 수 있는 이상적인 시스템 아키텍처 버전을 하나 출력해서 박제해둔다. 점진적으로 그 방향에 도달한다.
- 코드센스와 같은 정적 분석 도구는 코드베이스의 불안정한 부분을 신속하게 찾아준다.
Open Questions
- 나 또한 마이크로서비스 아키텍처를 경험하고, 일정 규모 전까지는 모놀리식이 낫다는 것을 깨달았다. 작은 단위부터 분해하기 위해서는 어떤 기준이 필요한가?
- 각 분해 패턴이 유용한 경우는 각각 어떤 상황인가?
- 교살자 무화과 패턴(strangler fig)
- 병렬 실행(parallel run)
- 기능 토글(feature toggle)
- 분해된 마이크로서비스에서 다른 테이블 데이터가 필요하다면?
Ch04. 마이크로서비스 통신 방식
TL;DR
- 마이크로서비스의 통신 유형은 동기식 블로킹, 비동기식 논블로킹, 요청 및 응답, 이벤트 기반, 공통 데이터를 고려할 수 있다.
- 이벤트 기반 통신은 마이크로서비스 아키텍처에 적절한 방식이지만, 협업 방식이 제한적인 경우 복잡성의 원인이 될 수 있다. 하나의 이벤트에서 확장하라.
Key Ideas
- 동기식 블로킹: 다운스트림 프로세스의 응답을 기다리는 호출이다. 익숙한 것이 장점이지만, 고유한 시간적 결합의 단점이 있다. (결제 서비스 이상거래 탐지 등)
- 비동기식 논블로킹: 공통 데이터 통신, 요청 및 응답, 이벤트 기반 상호작용 등의 방식이 있다. 호출 시 서비스를 블로킹하지 않는 장점이 있지만, 잘 알고 사용해야 한다. (장기 수행 프로세스)
- 공통 데이터 통신: 데이터를 정의한 위치에 넣고 다른 서비스가 그 데이터를 이용할 때 사용한다. 기본적으로 비동기이며, 간단한 구현과 많은 데이터 양을 수용 가능하다. 하지만, 결합의 원천이 될 수 있다. (프로세스의 상호 운용성)
- 요청 및 응답 통신: 요청을 보내고 응답을 받길 기대한다. 동기와 비동기 방식 각각 구현이 가능하다. (재시도 또는 보상 조치)
- 이벤트 기반 통신: 수신여부가 보장되지 않는 이벤트를 발행하고, 리스너가 이를 실행한다.
- 이벤트에 ID만 포함: 대상 서비스가 정보를 알아야 하는 경우 추가 도메인 결합이 생긴다.
- 자세한 이벤트: 자립적이며, 느슨한 결합을 가능하게 한다. 하지만 이벤트 크기와 민감 정보가 우려된다.
Open Questions
- 이벤트 기반 통신에서 하이브리드 기법을 사용한다면 어떻게? 그리고 언제?
Ch05. 마이크로서비스의 통신 구현
TL;DR
- 해결하고자 하는 문제로 기술을 결정할 수 있는지 고민하자. 그리고 선택된 기술 중 일부는 직렬화 포맷과 스키마가 결정되기도 한다.
- 어떤 선택이든 스키마 사용을 고려하자. 이는, 계약을 명시적으로 만드는 데 유용할 뿐만 아니라 우발적 중단 변경을 찾는 데도 도움이 된다.
- 마이크로서비스의 중단 변경을 피하기 위해 적절한 방법을 고민해자. 최대한 락스텝 배포를 피할 수 있는 방법을 찾아봐야 한다.
Key Ideas
- Remote Procedure Call: 로컬 호출을 통해 원격 서비스를 실행하는 기술. 클라이언트 코드 생성이 쉽지만 기술 결합, 취성 등의 문제가 있다. (동기식 요청 및 응답 모델에 적합)
- REST: 리소스 기준 요청 형식. HATEOAS도 있다. 중복과 성능 문제가 있을 수 있지만, 최근에는 다소 해결된 솔루션이 제공되었다. (다양한 클라이언트 액세스 허용 시 동기식 요청 및 응답 인터페이스에 적합)
- GraphQL: 맞춤형 서버 측 집계를 구현하지 않아도 된다. 하지만 클라이언트가 동적으로 변경되는 쿼리를 실행할 수 있어 성능 문제가 발생할 수 있으며, 캐싱이 복잡하다. (외부 클라이언트에 기능을 노출하는 시스템의 경계에 적합)
- Message Broker: 미들웨어이며, 프로세스 사이에서 프로세스 간 통신을 관리한다. 토픽과 큐가 주로 사용되며, 전달 보장과 순서 보장 및 쓰기 트랜잭션과 같은 주요한 특성이 있다.
- Serialization Format: 데이터 직렬화 및 역직렬화와 관련된 기술을 선택할 수 있다. 포맷으로는 크게 텍스트 포맷(XML, JSON), 바이너리 포맷(Protocol buffer, SBE 등)으로 나뉜다.
- Schema: 직렬화 포맷에 따라 정하게 되며, 계약 위반에 대해서는 대체적으로 구조적 위반(기존과 호환 불가)과 의미적 위반(동작의 변경)이 있다. 이를 해결하기 위해서는 테스트를 사용해야 한다.
- 마이크로서비스의 중단 변경을 피하기 위해서는 확장 변경(expansion changes), 관대한 독자(tolerant reader), 올바른 기술(right technology), 명시적 인터페이스(explicit interface)와 같은 아이디어가 있다.
- 중단 변경이 필요하다면 락스텝 배포(lockstep deployment), 호환되지 않는 마이크로서비스 버전의 공존, 기존 인터페이스 에뮬레이션과 같은 선택지가 있다.
- 마이크로서비스가 어디 있는지 알고 싶다면, 서비스 디스커버리를 알아보라. DNS, Dynamic Service Registry(Zookeeper), Consul, etcd & K8S, Eureka 등
Open Questions
- 내가 경험한 서비스에서 사용한 REST, GraphQL, Message Broker 기술은 그 상황에서 적절한 선택이었을까?
- 내가 지금 경험하고 있는 서비스의 중단 변경은 어떤게 있을까?
- 내가 지금까지 경험했던 중단 변경 대응 방식은 어떤게 있었을까?
- 서비스 메시와 API 게이트웨이. 두 방식 중 상황 별로 적합한 선택은 어떨까?
Ch06. 워크플로
TL;DR
- 워크플로를 구현하는 방법은 구현할 비즈니스 프로세스를 명시적으로 모델링하는 것이다.
- 분산 트랜잭션은 최대한 피하자. 첫 번째 방법은 데이터를 분리하지 말되, 정말 분해해야 한다면 사가를 고려하자.
Key Ideas
- ACID
- Atomicity(원자성): 트랜잭션 내에서 시도된 작업이 두 가지 상태, 즉 모두 완료한 상태거나 모두 실패한 상태인지 확인한다. 어떤 이유로든 시도한 변경이 실패하면 전체 연산이 중단되고 아무것도 적용되지 않은 것처럼 보인다.
- Consistency(일관성): 데이터베이스가 변경되면, 유효하고 일관된 상태가 유지된다.
- Isolation(격리성): 여러 트랜잭션이 간섭없이 동시에 작동할 수 있다. 이는 어떤 트랜잭션 중에 이뤄진 모든 중간 상태 변경이 다른 트랜잭션에 안 보이게 하는 방법으로 달성된다.
- Durability(내구성): 일단 트랜잭션이 완료되고 나면 시스템 오류가 발생하는 상황에서도 데이터가 손실되지 않는 것을 보장한다.
- Two-Phase Commit
- Voting: coordinator는 트랜잭션에 참가할 모든 worker에 연락하고 일부 상태 변경이 가능한지 여부를 확인 요청한다.
- Commit: 모든 워커가 변경에 동의한 경우, 커밋 단계에서 실제 변경이 일어나고, 잠금이 해제된다. 찬성하지 않은 경우 모든 당사자에 롤백 메시지를 보내 로컬에서 정리하도록 보장한다.
Saga Pattern
Failure: 역방향 복구에는 실패 복구와 이후에 일어나는 정리 작업인 롤백이 포함되며, 정방향 복구는 실패가 발생한 지점에서 데이터를 가져와 계속 처리한다.
- 모든 단계가 단일 데이터베이스 트랜잭션이면, 간단한 롤백으로 정리할 수 있다. 하지만 그렇지 않다면, 보상 트랜잭션(compensating transaction)을 구현해야 한다.
- 하지만, 트랜잭션이 일어나지 않은 것처럼 만들 수는 없기 때문에 의미적 롤백(semantic rollback)이라고도 한다.
- 롤백을 줄이기 위해서는 워크플로의 단계를 재정렬 할 수도 있다.
Orchestrated Saga: central coordinator를 사용해 실행 순서를 정의하고, 보상 조치를 트리거 한다.
- 너무 많은 중앙 집중화를 피하기 위해서는 서로 다른 흐름에 대해 서로 다른 서비스가 오케스트레이터 역할을 수행하도록 하는 것이다.
- 본질적으로 이 방식은 어느 정도 결합된 방식이며, 서비스에 전달돼야 할 로직이 오케스트레이터에 흡수되기도 한다는 점을 고려해야 한다.
Choreographed Sage: 여러 협력 서비스 사이에서 사가 운영에 대한 책임을 분산시키는 것을 목표로 한다.
- 모든 서비스는 상대 서비스에 대해 전혀 모르도록 설계가 가능하다. 도메인 결합도가 낮은 아키텍처를 만들 수 있다.
- 어떤 일이 발생하는지 파악하기 더 어려워질 수도 있다. 이 경우에는 사가에 대한 고유 ID, 즉 correlation ID를 생성해 방출되는 이벤트에 넣어 추적할 수 있다.
Open Questions
- Orchestrated Saga VS Choreographed Saga
- 오케스트레이션형 사가에서 요청 및 응답 호출 방식이 더 많이 사용되는 반면, 코레오그래피형 사가에서는 이벤트 방식이 더 많이 사용되는 경향에 대해 유의할 필요가 있다.
- 필자는 한 팀이 전체 사가 구현을 담당하는 경우에는 오케스트레이션형 사가, 여러 팀이 관여하는 경우 코레오그래피형 사가를 선호한다.
ADR001. Monolithic to Microservices
Context
- 현 시점으로 내가 경험하고 있는 서비스(루티 프로)는 마이크로서비스로 전환하기에 적합한가?
- 현재 서비스는 마이크로서비스로 전환하기에 적합하지 않다.
- 가장 큰 원인으로는, 관리하는 사람이 절대적으로 적다.
- 총 7명이 해당 서비스에 집중하고 있으며, 백엔드 개발자 2명, 프론트엔드 개발자 2명이다.
- 각 서비스로 쪼갠다면 이 서비스를 관리하기에는 리소스가 턱없이 부족하다.
- 일부만 전환해야 한다면, 어떤 것을 먼저 전환해야 할까?
- 그럼에도 필요에 의해 일부를 분리할 수 있을 것이다. 첫 번째 대상으로는 엑셀 서비스가 될 것이다.
- 엑셀 서비스는 대량의 데이터를 수용할 수 있어야 하며, 적지 않은 수의 스레드를 점유한다.
- 또한, 후속 처리가 필요한 이벤트가 있기에 한 번 엑셀이 업로드되면, 다른 유저에게 지연이 발생하게 될 것이다.
- 이를 해결하기 위해서 해당 서비스를 분리해 트래픽을 분산할 수 있다.
Decision
- 엑셀 서비스를 분리한다.
- 템플릿과 관련 라이브러리는 엑셀 서비스로 옮긴다.
Consequences
- API Endpoint 변경이 필요하다.
- Strangler Fig: 점진적 마이그레이션을 위해 처음엔 API Endpoint는 유지하고, 기존 서비스 내에서 엑셀 서비스를 호출하는 방식 적용한다.
- 배포 및 부하 독립성을 확보한다.
- 기존 디비는 유지하고, 엑셀 서비스에서 해당 디비를 접근하도록 한다.
- 추후 이벤트 기반 통신으로 변경하면, 각 데이터를 이벤트로 전송한다.
ADR002. Domain Coupling
- createdDate: 2025-10-23
- updatedDate: 2025-10-24
Context
우리 서비스에서 현재 발생하는 도메인 결합이 어떤게 있는가?
Order, Dispatch 간 결합
- 기본 설명
- Order: 유저의 요청
- Dispatch: 유저의 요청을 배송하기 위한 차량 배차
- 우리 서비스 역사적으로 가장 유명한 결합이다.
- 과거에는 dispatch 내 orderId 를 두어 결합이 발생했다. 현재에는 mapping 테이블을 두고 서로를 매핑하고있어 이전보다는 느슨해졌다.
도메인 결합을 점진적으로 줄여나가기 위해서는 어떤 방식을 채택해야 하는가?
- 결합
- 상태 변경이 해당한다. order와 dispatch는 서로의 상태에 관심이 있다. dispatch가 confirmed로 변경되면, order는 ready로 변경된다.
- 해당 상태를 컨트롤하는 API는 여러 개로 분산되어있다.
- 애초부터 이 둘의 상태 연관관계가 맺어진 것이 문제였을까? 점진적으로 나아가기 위해서는 어떤 방식이 필요한가?
- 사실 현재 서비스에서도 상태의 연관은 맺어져있지 않다.
- 다만 service layer에서 두 상태를 관리하는 주체가 있다.
- Service layer에서는 domain layer로 요청만 보내야 하지, 값을 직접 컨트롤하면 안 된다.
- 이는 내용 결합으로 표현될 수 있다.
- 둘은 상/하 관계일까? 동등한 관계일까?
- 지금은 동등한 관계라고 표현할 수 있을 것 같다.
Decision
- 상태 변경 로직은 domain model에 응집한다.
- Service layer에서 값을 직접 입력하는 것이 아니라, domain model 메소드에 요청하도록 한다.
- 상태 변경 관련 비즈니스 규칙은 domain model만 확인하면 된다.
- 분산된 상태 변경 관련 service 및 api를 응집한다.
- 처음부터 합치지 않고, api endpoint를 만들어서 client가 모두 해당 endpoint를 호출할 수 있도록 유도한다.
Consequences
- 유저가 상태 변경을 못 할 수도 있다. 기존 API를 사용하는 client에서 변경된 API를 더이상 사용하지 못할 수 있다. 놓치지 않도록 한다.
- 각 api, service 별 상태 변경 로직이 다를 수 있다. 이는 잘 파악해서 먼저 도메인 로직에 응집할 수 있도록 한다.
ADR003. Migrate to Excel
- createdDate: 2025-10-26
- updatedDate: 2025-10-26
Context
엑셀 서비스가 별도로 구성되었다고 가정한다. 다음과 같은 상황 및 결정이 있다.
- 운영 중인 서비스이며, 가장 높은 신뢰성이 요구되는 서비스이기 때문에 한 번에 옮기기에는 리스크가 크다고 판단했다.
- 기능이 계속해서 추가되고 변경되기 때문에 옮겨진 서비스가 방치되는 시간이 길어지면 안 된다.
- 비용의 부담으로 인해 데이베이스 분리 없이 기존 데이터베이스를 연결한다.
- 주로 엑셀 업로드 시 부하가 발생하기 때문에 해당 부하를 분산하기 위해 엑셀 서비스를 우선적으로 분리한다.
Decision
- 한 번에 옮기기에는 리스크가 있다고 판단하기 때문에 점진적 마이그레이션을 택한다. 대신, 이상적으로 생각하는 아키텍처의 결과를 항상 되뇌자.
- 점진적 기능 추가 및 마이그레이션에 유용한 교살자 무화과 패턴을 사용하자. 기능이 계속해서 추가되고 변경되기 때문에 코드를 중복해서 서비스를 구동하고, 이를 유지하기에는 부담이 있기 때문이다.
- API 엔드포인트는 유지하고, 서비스를 하나씩 옮기자. 옮겨진 서비스는 새로운 API 엔드포인트를 갖게 되고, 기존 호출되던 API는 새로운 API를 호출하게 하자.
- 기존 서비스와 엑셀 서비스에서 의존하던 코어 모듈은 공통적으로 사용할 수 있도록 라이브러리화 한다.
- 서비스가 모두 옮겨진 후 문제가 발생하지 않는다면, 해당 엔드포인트를 프론트엔드에서 최종 변경한다.
Consequences
- 새로운 API 호출 시 규격 일치
- 엑셀에 의존하는 기존 서비스 파악
- 코어 모듈 라이브러리화
ADR004. Is REST the best?
- createdDate: 2025-10-30
- updatedDate: 2025-10-30
Context
현재 우리 서비스에서 REST는 적절한 선택이었을까?
대부분의 기능에 대해서는 적절하다고 생각한다. 특정 도메인의 생성을 요청하고, 수정 및 조회에 따른 결과물이 각 페이지 별로 제공되기 때문이다. 그리고 이 과정에서는 동기적인 요청 및 응답 방식이 요구되고, 사실상 클라이언트와의 통신에 있어서 표준 방식으로 요구되기 때문이다.
하지만 일부 기능에 대해서는 적절하다 생각하지는 않는다. 이를테면,
- 엑셀 업로드
- 엑셀 업로드는 요청과 응답이 꼭 동기적인 방식으로 이루어지지 않아도 괜찮다. 물론, 엑셀의 기본 검증 결과는 반환받아야 하지만, 그 이후의 일은 신경쓰지 않아도 되기 때문이다.
- 사용자에게는 기본 검증이 완료되면 무조건 올라간다는 보장을 제공해야 하고, 서비스 또한 그렇게 동작해야 한다. 기본 검증 이후에는 기다릴 필요가 없다는 뜻이다.
- 그러기 위해서는 엑셀의 비동기성 처리가 필요한데, 이를 메시징으로 처리할 수 있다는 생각이 든다. 각 데이터를 메시지로 발행하고, 이를 소비하면 각 데이터가 생성되는 방식이다.
- 하지만 실패한다면 모든 데이터의 트랜잭션 처리는 어쩌면 좋을지에 대해 고민이 필요하다.
- 각 페이지 별 조회
- 각 페이지 별 조회가 있지만, 일부 상이한 필드로 인해 여러 개의 API를 생성하고 있다. 이는 유지관리의 생산성에 크게 도움되지 않는다.
- 클라이언트에서 요구하는 필드는 상이하지만, 값이 같다면 GraphQL을 고려해봐도 괜찮을 것 같다. 페이지에 상관없이 조회하고자 하는 도메인 데이터는 조합해서 불러올 수 있기 때문이다.
- 하지만 아직 고민이 필요한데, 수정 및 생성에 있어서 GraphQL은 적절하다는 생각이 들지 않는다. 통일성 측면에서도 기존 REST로 자리잡힌 인터페이스와 달라져, 리소스가 적게 들지는 않을 것 같다.
Decision
- 엑셀 업로드 시 각 데이터 별로 메시지를 발행하는 방식을 사용한다.
- 별도 큐를 사용하기에는 비용적인 측면을 고려해야 하므로, 스프링 내부 이벤트를 발행하도록 한다.
- 엑셀 업로드 로직은 특정 도메인의 데이터를 생성하기 위해 특별한 작업을 하지 않고, 이벤트만 발행하면 되므로 결합도를 낮추는데 도움이 된다.
Consequences
- 엑셀 업로드 시 특정 데이터에 오류가 났을 때, 트랜잭션 처리가 제대로 되는지 확인이 필요하다. 그리고 이를 사용자가 인지할 수 있도록 해야한다.