단일 데이터베이스를 사용하고 트랜잭션 처리가 완벽하게 지원된다(ACID)
마이크로서비스는 각 서비스마다 독립적인 언어를 사용할 수 있고 독립적인 DB를 선택할 수 있다는 특징이 있다.(Polyglot)
또한 마이크로서비스는 해당하는 데이터베이스가 가지고 있는 데이터를 자신이 가지고 있던 정보처럼 JDBC로 접근하는 것은 원칙적으로 금지되어 있다. -> API를 사용하여 통신해야 함
마이크로서비스에서는 모놀리식에서 지원했던 트랜잭션 처리 방법을 완벽하게 구현하는 것은 어렵다.
예를 들어 Commit Transaction은 어떠한 작업 단위가 정상적으로 끝났다는 것을 의미한다.
예를 들어 ORDER-SERVICE에서 CATALOG-SERVICE로 주문을 한다고 가정한다.
우리가 주문을 하면 해당하는 데이터를 주문 데이터베이스 테이블에 저장한다. 하지만 트랜잭션을 완벽하게 처리하기 위해서는 주문을 했을 때 발생되었던 주문 데이터를 바로 데이터베이스에 반영해 주는 것이 아니라 주문한다는 얘기는 결제라는 것이 포함될 수 있고, 주문을 하기 위해서 적절한 수량이 있는가 체크를 해봐야한다.
ORDER-SERVICE에서 주문을 확정하는 것이 아니라 주문을 일단 대기상태로 만들어 놓고 CATALOG-
SERVICE에 가서 해당하는 데이터가 주문을 할 수 있는지 재고 여부를 따져보는 시나리오를 가져보자.
ORDER-SERVICE에서 CATALOG-SERVICE로 주문을 보내게 되면 Kafka에 전달된 주문은 다시 CATALOG-SERVICE가 읽어 들여진다. 우리는 해당하는 주문 만큼 CATALOG-SERVICE에서 -를 시켰었는데, 바로 -를 하는 것이 아니라 수량체크를 한다고 가정하자.
현재 가지고 있는 재고 수량이 주문한 수량만큼 사용될 수 있는 만큼 남아 있다면 정상적인 UPDATE가 되면 된다.
-> ORDER-SERVICE의 주문 상태를 PENDING에서 CONFIRMED로 바꿔준다.
이렇게 해줌으로써 주문이 하나의 트랙잭션을 이루는 것 처럼 수량체크라던가 결제 체크를 담고 처리를 하면서 문제가 생기는 부분이 발생할 경우 처음에 실행되었던, 주문을 Rollback 시키거나 취소시키는 형태로 갈 수 있다.
Rollback Transaction 상황을 가정해 보자.
만약 수량 부분에서 가지고 있는 재고가 없다고 가정해보자.
그럼 CATALOG-SERVICE에서 취소 message를 보낸다.
해당 취소 message가 ORDER-SERVICE에 전달이 됨으로 써 자신이 가지고 있었던 PENDING 상태 값을 CANCELED 상태로 바꿔준다.
💡 EventSourcing에서는 INSERT라는 개념만이 존재하고 UPDATE나 DELETE라는 개념은 존재하지 않는다고 보는게 편하다.
예를 들어 ORDER-SERVICE에서 현재 가지고 있는 데이터베이스라고 하면 최종적으로 주문이 확정되었다라는 결과만 남아있을텐데, 만약에 Event Sourcing에 관련된 기능을 쓰게되면, 해당하는 주문상태가 처음 만들어지고 승인이되고 배송이되는 모든 정보까지 트랙킹할 수 있는 정보가 히스토리화 되서 남아 있다고 볼 수 있다. 사용자 측면에서는 이렇게 남아 있었던 모든 값들을 하나씩 하나씩 되짚어 보면서 마지막 상태값을 알 수 있게 된다. 데이터를 단순화 시켜줄 수 있다는 부분과 마이크로서비스 같이 분산되어 있는 작업에서는 기존에 있었던 방법보다는 효율적으로 데이터 처리가 가능해진다.
Event Sourcing에서 또 하나 나오는 개념중에서 도메인 주도 설계(Domain-Driven Design)라는 개념이 있다. 도메인 주도 설계에서는 데이터 상태값을 바꾸기 위한 방법(Aggregate)과 현재 가지고 있는 상태값이 어떤 것인지 표현줄 수 있는 Projection 이 2가지 개념이 사용이 된다.
우리가 사용해봤었던 Kafka라는 메시지 중심의 비동기 작업 처리를 가능하게 해준다.
단점이라면 상태 값 하나만 가지고 있는 것이아니라 데이터가 그 상태값을 갖기위한 모든 트랜잭션을 다 기록하기 때문에 데이터가 만약 1000번이 바뀌었다고 가정하면 1000번의 트랜잭션을 다시 봐야한다는 문제가 생긴다.
그래서 모든 이벤트에 대한 복원에 시간이 걸리고 있기 때문에 "스냅샷"이라는 개념을 도입을 해서 예를 들어 1번부터 100번까지 트랜잭션을 가지고 있고, 101번부터 200번까지의 트랜잭션을 가지고 있는 식으로 데이터를 잘라서 저장하게 되면 이런 문제를 해결할 수 있다.
다양한 데이터가 조회됬을때 문제는 "CQRS"를 통해 해결하고 있다.
Client가 어떤 application을 통해서 Kafka Message Topic에다가 데이터를 전달을 하게 되면, Kafka에서는 해당하는 데이터 값을 다양한 형태의 서비스라던가 System이라던가 다른 마이크로서비스에 전달을 해줄 수 있는 기능이다. 이러한 Kafka가 가지고 있는 메시징 전달 기능과 소비기능을 이용을 해서 CQRS라는 방법을 처리할 수 있다.
CQRS라는 것은 Command and Query Responsibility Segregation이라고 해서 명령과 조회에 각각 책임을 분리할 수 있는 기능을 얘기를 한다. 쉽게 말해서 상태를 변경하는 Command, 조회를 담당하는 Query라는 파트를 분리를 해서 각각 작업을 처리를 해주겠다는 것이다.
그림을 보면, 앞에 어플리케이션에서 데이터를 업데이트할 수 있는 어플리케이션과 데이터를 읽어들일 수 있는 기능의 어플리케이션을 각각 분리해서 개발을 했다고 가정을 한다.
데이터를 업데이트할 수 있는 기능에서는 Event Sourcing에서 사용되었던 것 처럼 Event에 대한 정보를 모두 기록한다고 가정한다. 그리고 해당하는 이벤트 값을 Kafka의 Event Handler에서 Event를 캐치한 다음에 상태를 저장하기 위한 DB에 그 값을 반영했다고 하자.
이렇게 변경된 데이터 값을 사용하는 측면에서는 조금전 사용되었던 write interface가 아니라 read interface를 이용을 해서 저장되어 있는 상태값만 가져가게 되면 철저하게 우리가 작업하려고 하는 상태를 변경하는 부분과 데이터를 조회하는 부분을 나눠서 개발할 수 있다.
이렇게 데이터를 분리해서 만들어 놓으면 분산되어진 마이크로서비스라 하더라도 같은 상태값을 유지시킬 수 있을 것이고 데이터의 구조를 간단하게 만들 수 있을 것이다.
먼저 사용자가 POST 방식으로 /orders를 전달했다고 가정.
API Gateway가 해당 API를 제일 먼저 받아서 ORDER-SERVICE로 전달을 한다.
ORDER-SERVICE에는 해당 주문을 Update하거나 Create하거나 Delete 하는 기능이 있다고 가정.
주문자체의 값은 조회할 수 있는 기능도 있다고 가정.
ORDER-SERVICE에서 MariaDB라는 DB에 데이터를 저장할 것이다.
Command Model을 이용해서 Update를 하게 된다.
Update 주문이 들어왔으면 그 값을 Kafaka Topic에 데이터를 저장한다.
Kafka Topic에 저장된 데이터 값은 연결되어있는 다른 마이크로서비스라던가 이메일 시스템 다른 시스템으로 데이터를 전달할 수 있을 것이고, Event Handler에서 Kafka Topic에 전달된 값을 캐치하고 있다가 해당하는 값을 실제로 DB에 업데이트 해주면 된다.
따라서 Event Sourcing에서 말했던 것 처럼 상태 값을 전부 Topic에 기록할 수 있고, Topic에 기록된 데이터 값을 실제 마지막 상태 값에 우리가 DB에 반영하는 것이 가능해 진다.
데이터를 사용하는 Query Model
DB에 있는 최종 상태의 값만 가지고 와서 해당하는 값을 Client에 전달을 함으로써 Client는 마지막에 있었던 상태값을 가지고 주문된 내역이 어떤 것인지 확인해 볼 수 있다.
Saga Pattern이란 Application이 분리된 경우에 각각의 Application은 자신만의 Local transaction만 처리하겠다는 개념이다.
우리가 만들었던 마이크로서비스는 각각의 기능별로 작게 쪼개진 기능 어플리케이션을 의미를 한다.
Saga Pattern에서 얘기하는 방식과 가장 적합하게 Transaction을 처리할 수 있는 경우이다.
또 하나의 특징은 각각의 Application에 대한 작업이 하나로 끝나는 것이 아니라 우리가 만들었던 ORDER-SERVICE, CATALOG-SERVICE 처럼 연쇄적인 반응으로 이어지는 Transaction이라고 할 경우 하나의 마이크로서비스에서 문제가 생겼다면 이전에 발생되었던 모든 값들을 원상복구 시키는 작업이 필요하다. 이런 것을 Saga Pattern에선ㄴ 보상 Transaction이라고 한다.
쉽게 말해서 Transaction을 실패했을 경우에 이전상태로 돌려 줄 수 있는 Rollback 처리를 위한 보상 Transaction이 준비 되어 있다는 것이 특징이다.
데이터의 원자성은 보장하지는 않지만, 일관성을 보장해주기 때문에 마이크로서비스같은 구조에서 Transaction을 유지하기 위해서 적합한 패턴이라고 볼 수 있다.
왼쪽의 그림은 단일 어플리케이션에서 사용했었던 2단계 Commit에 대한 부분이고 오른쪽이 Saga Pattern에 대한 부분이다. ORDER-SERVICE와 CUSTOMER-SERVICE와 다시 ORDER-SERVICE로 이어지는 하나의 일련된 과정에서 각각의 서비스들은 자신만의 Transaction을 처리해 줌으로써 해당하는 Transaction이 정상적으로 진행이 됐다, 그렇지 않다라는 값들만 다음 서비스로 전달해주는 그림으로 볼 수 있다.