나의 OhSir39cm E-Commerce 프로그램에는 특정 한 부분에 @Transactional 어노테이션이 걸려있다.
커머스에서 가장 중요한 부분이라고 할 수 있는 주문&결제 부분인데,
기존에는 주문과 결제가 성공하면 재고를 차감하고 외부 데이터플랫폼으로 전송 해 주는 형태이다.
├─order
│ ├─application
│ │ └─dataPlatform
│ ├─domain
│ ├─infrastructure
│ │ ├─jpaRepository
│ │ └─repositoryImpl
│ └─presentation
│ └─dto
파일 구조는 이렇게 되어있고 presentation > infrastructure > domain > application 으로 가는 레이어드 클린 아키텍처로 이루어져있다.
각 기능을 독립적인 계층으로 구분하고있어서 관심사의 분리를 명확하게 하였고, 각 계층이 특정한 역할을 맡도록 설계가 되어있다.
@Transactional
function createOrder(Long customerId, List<Order> order){
유저 잔고 조회();
재고 조회();
주문 저장();
재고 차감();
데이터플랫폼 전송();
}

주문 서비스는 위와 같은 구조로 이루어져있다.
얼핏보기엔 문제없어보이지만 주문이 모두 성공해도 주요 상품주문이라는 로직과 상관없는 외부 연동 시스템(ex. 주문 성공 시 알림톡을 보낸다.) 데이터플랫폼 전송에서 실패하면 앞에 주문로직이 모두 성공을 해도 Rollback이 되는 상황이 발생하게 된다.


SlowRead작업으로 인해 요청처리에 영향을 줄 수 있다. 
externalApi의 타임아웃으로 트랜잭션을 롤백시켰으나, external서비스에서는 사실 정상적으로 처리되었다면.
주문이 없는데 externalAPI에는 데이터가 정상적으로 전송이 되었을 때 무결성 문제가 발생할 수 있다.
일단 근본적인 문제는 '주문'이라는 비즈니스 로직에 너무 많은 책임이 주어져 있다.
주문생성 > 주문하는 고객 잔고 조회 > 재고 확인 > 결제 > 재고차감 > 외부 API에 데이터 전송
너무 많은 관심사를 하나의 작업으로 처리하면 위와같은 문제가 발생할 수 있다.
(Event Delivery) : 어떻게하면 효율적으로 정보를 전달할 수 있는가?
동기식 이벤트 처리방식이 가지고 있는 대기와 지연에 대한 문제를 이벤트기반의 비동기 방식으로 유연성 있고 확장성 있게 처리하기 위한 방식
여러 마이크로서비스를 조합하는 비즈니스 기능의 구현을 이벤트 기반의 비동기 통신으로 합성하는 패턴
유연성, 확장성, 변경비용을 고려해서 서비스들간의 낮은 결합도(Decoupled)와 비동기 통신이 필요.
해결책 : 다른 마이크로서비스를 능동적으로 직접 호출하지 아낳고 이벤트와 메시지를 기반으로 반응 모드로 작동
이벤트 구독하고 있는 서비스들이 개별적으로 구독해서 독립적으로 수행하는 방식

분산 application에서 여러 application에 걸쳐져 있는 트랜잭션들을 조정해서 데이터의 일관성을 유지해주기 위한 분산 트랜잭션 관리 패턴.
로컬 트랜잭션으로 각각 분리해서 실행 > 실패했을 경우 보상트랜잭션 발생시켜 데이터 일관성을 유지함.

상품 주문 결재 예시를 saga패턴으로 나타낸 그림이다.
주문 생성 > 재고 차감 > 결제 > 쿠폰 승인 > 포인트 차감 > 주문완료
이 프로세스에서 실패하는 상황이 발생한다면 ReleaseStock()으로 보상트랜잭션을 실행시키고 상품을 취소한다.
성능 향상
모든 트랜잭션을 한 번에 처리하는 대신, Saga는 각 서비스에서 독립적으로 트랜잭션을 처리한다.
이렇게 함으로써 트랜잭션을 나누어 병렬로 처리할 수 있어 성능을 높일 수 있다.
트랜잭션 복구 용이
하나의 서비스가 실패할 경우, 해당 서비스의 작업을 취소하거나 되돌리기 위한 보상 트랜잭션을 정의할 수 있다. 실패한 작업에 대해 다른 서비스들이 자동으로 복구 작업을 수행하므로, 시스템의 신뢰성을 높일 수 있다.
서비스 간 결합도 낮추기
각 서비스가 독립적으로 트랜잭션을 처리하고, 다른 서비스와의 의존도를 최소화하기 때문에 서비스 간의 결합도가 낮아지고, 시스템의 유연성과 유지보수성이 향상된다.
에러 처리가 명확함
Saga 패턴은 실패 시 롤백을 통해 시스템의 일관성을 유지한다.
실패가 발생하면 각 서비스가 보상 트랜잭션을 통해 문제를 해결할 수 있어, 에러를 처리하는 로직이 명확하고 효과적이다.
비동기적 처리
Saga 패턴은 각 서비스가 비동기적으로 트랜잭션을 처리하기 때문에, 다른 서비스의 응답을 기다리는 동안 다른 작업을 진행할 수 있어 시스템의 효율성을 높인다.
대규모 시스템에 적합
마이크로서비스 아키텍처에서 서비스가 분산되어 있을 때, 전체 시스템의 일관성을 유지하는 데 유리한 패턴이다. 각 서비스가 개별적으로 트랜잭션을 처리하므로, 시스템 확장성이 높고, 장애 발생 시 영향을 최소화할 수 있다.
MSA(Micro Service Architecture)라는 것이 관심사가 분리된다는 점에서는 아주 좋지만 트랜잭션이 여러 마이크로서비스에 걸쳐있어 처리가 복잡하다는 단점이 있다.
서비스가 작을 때에는 한 트랜잭션이 오래걸린다거나 중요하지 않은 로직에의해 모든 작업이 롤백되는 한이 있더라도 크게 영향을 받지 않아 전체 소스 관리 측면에서는 "빠른 원인분석" 및 수정이 가능하여 이점이 있으나,
서비스 확장이 필요한 순간에는 어쩔 수 없이 트랜잭션을 쪼개야하는 상황이 발생하게 된다.
나의 이커머스 프로젝트에도 관심사 분리를 적용하기 위해 @TransactionalEventListener를 적용하였다.
@Transactional
function createOrder(Long customerId, List<Order> order){
try{
유저 잔고 조회();
재고 조회();
주문 저장();
재고 차감();
eventPublisher.publishEvent(new 데이터플랫폼 전송(source, 주문정보));
}
catch(e){
log.warn(e);
}
}
🔽 비동기로 이벤트 발행하기
@Async
@TransactionalEventListener(phase= TransactionPhase.AFTER_COMMIT)
public void sendOrderMessage(OrderEvent event) {
List<OrderProductRequest> order = event.getOrder();
log.info("주문 데이터가 전송되었습니다: " + order);
}
이렇게 짜면 위의 비즈니스 로직과는 무관한 이벤트 발행이 된다.
SAGA Pattern도입을 통해 데이터의 일관성을 유지하면서 확장에는 유연한 서비스를 만들고자 한다.