Reaction to Saga with Springboot & Kstream
Tags: saga, springboot, kafka, kafkastreams
Published: 2022-02-09 18:25:56

Today an article about building a saga transaction system with springboot and kafka streams reached to me, and of course I felt very curious about it, so I got to work inmediately. The article is [this ](https://github.com/piomin/sample-spring-kafka-microservices/blob/streams-full)

At first look there exists _two modes_ : full and semi kafka streams. I´ll look for the difference...

After taking a look to the readme and the picture, now I see that it seems an event driven arch where microservices publish & consume commands. We have bounded contexts and each domain is independent from the others. I see...

Now a question: if this is a Saga, how is the rollback made (or published, or executed or whatever) 
As far as i know, a [Saga transaction](https://microservices.io/patterns/data/saga.html) is a step made in a bussines flow, but in the picture I´m seeing a join between 2 streams where 2 different microservices publish the _transaction ok_ command. What happens when one of them fail? Lets see...

Going deeper, in the full streams version of the github, the solution considers a ktable, but it's used as a store for the orders, and is the component of this solution from where the controller layer retrieve its data to expose it as an API. Still looking for the rollback system...

I found it! But I think that it consists in a local mechanism. I´m still thinking that we are losing de distributed rollback transaction where one of the two domains has to emit the rollback event.

**Edit 1:** Yesterday I ran out of time

The magic is done [here ](https://github.com/piomin/sample-spring-kafka-microservices/blob/streams-full/payment-service/src/main/java/pl/piomin/payment/PaymentApp.java)-> 

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5fqkxbgvofzkng37a3dy.png)
 
In this example what is being done is to check the number of reservations(randomly updated) to publish a ok command or ko to the result stream if there exists enough stock.

In this example something that took my attention was the way is being replace a common business layer (@_repository @service_ ) with a materialized state store where the reservartions are being stored.

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iagvoyrd750wpuxym00c.png)

Here, for each order that is being consumed from the _orders_ stream, it´s being stored in the state stored grouped by customerId that is the key and processed by the Aggregator service, which is the one who decides what to do with the order: confirm, reject or process, based in the status of the order.

But now I have another question to the fist one (how is the distributed rollback made?): How do I know the status of the order? It is not shared between all the microservices instances, because as I just see, the order event it´s being consumed from the stream. So let´s see.

Reaching out to the [place ](https://github.com/piomin/sample-spring-kafka-microservices/blob/streams-full/order-service/src/main/java/pl/piomin/order/service/OrderGeneratorService.java) where the orders are being genereated I confirmmed that everything is ramdomly generated but the status, it´s being hardcoded to 'NEW'.

So, now we are here:
![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eedb3ovos0telamdb0f3.png)
 
I see... Now going to check if[stock service](https://github.com/piomin/sample-spring-kafka-microservices/blob/streams-full/stock-service/src/main/java/pl/piomin/stock/StockApp.java) does it´s things in the same way, the answer is yes, but slightly different implemented, resulting in something like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mrabc5cf7t38qwbdjgxw.png)

Now that I know more of what I´m seeing I think that this piece of code that exists in the stock service and payment service does the distributed rollback or commit magic:

![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3jz0vf32oy1eeh9o6l85.png)

Now lets look for the _CONFIRMED _& _ROLLBACK_ command. First of all the join function:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tfs2ysnp1m3oxa0boi3t.png)

So, definetively is very simple: 

1. Join between payment stream and stock stream by order_id
2. Check the status of both payment event and stock event
3. Publish the result command (Confirm,rollbak) into the orders stream (the initial stream)

The business flow ends like this

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qwmdfwu7bng77oa7q0on.png)
 
So yes, all distributed transactions are published, processed and consummed in an event driven way. Now I have my answer, and this poc has been analyzed.

**BUT**
I know that this is a POC, but what happens when something fails? A very clear weakpoint is located in the join between the orders service. As we are working in a distributed async architecture we have to deal with eventual consistency (or consistency that will never happen, you know). In a event streaming ecosystem, reactivity and realtime decissions are common and they are what make this kind of architecture to have sense, so I´m thinking that stock should be updated in realtime.

So the questions are: 
what happens if payment fails? the stock goes inconsistent
what happens if stock fails? the payment is never confirmed neither cancelled. The bank would block client's money for a while.

This join is never been accomplished:
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t0cr9fvlqpjdvqcrdyof.png)
 
 
To forward events that never join, punctuate() can be used to periodically go through a store and emit events that don't have a match and have been in the statestore for a while.

But there were some [petitions ](https://issues.apache.org/jira/browse/KAFKA-10847) to the kafka team for making it [easier](https://docs.confluent.io/platform/current/streams/javadocs/javadoc/org/apache/kafka/streams/kstream/SlidingWindows.html#ofTimeDifferenceAndGrace-java.time.Duration-java.time.Duration-):

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d1xki6sr2s7dkhx8x6bi.png)

So I will give a try. Stay tunned.