]> juplo.de Git - website/blob
a614c261bcf176bef46bfdfabc2e57de929bd9e1
[website] /
1 ---
2 _edit_last: "2"
3 author: kai
4 categories:
5   - demos
6   - explained
7   - java
8   - kafka
9   - spring
10   - spring-boot
11 classic-editor-remember: classic-editor
12 date: "2021-02-05T17:59:38+00:00"
13 guid: http://juplo.de/?p=1201
14 parent_post_id: null
15 post_id: "1201"
16 title: 'Implementing The Outbox-Pattern With Kafka - Part 0: The example'
17 url: /implementing-the-outbox-pattern-with-kafka-part-0-the-example/
18
19 ---
20 _This article is part of a Blog-Series_
21
22 Based on a [very simple example-project](/implementing-the-outbox-pattern-with-kafka-part-0-the-example/)
23 we will implemnt the [Outbox-Pattern](https://microservices.io/patterns/data/transactional-outbox.html) with [Kafka](https://kafka.apache.org/quickstart).
24
25 - Part 0: The Example-Project
26 - [Part 1: Writing In The Outbox-Table](/implementing-the-outbox-pattern-with-kafka-part-1-the-outbox-table/ "Jump to the explanation what has to be added, to enqueue messages in an outbox for successfully written transactions")
27
28 ## TL;DR
29
30 In this part, a small example-project is introduced, that features a component, which has to inform another component upon every succsessfully completed operation.
31
32 ## The Plan
33
34 In this mini-series I will implement the [Outbox-Pattern](https://microservices.io/patterns/data/transactional-outbox.html)
35 as described on Chris Richardson's fabolous website [microservices.io](https://microservices.io/).
36
37 The pattern enables you, to send a message as part of a database transaction in a reliable way, effectively turining the writing of the data
38 to the database and the sending of the message into an **[atomic operation](https://en.wikipedia.org/wiki/Atomicity_(database_systems))**:
39 either both operations are successful or neither.
40
41 The pattern is well known and implementing it with [Kafka](https://kafka.apache.org/quickstart) looks like an easy straight forward job at first glance.
42 However, there are many obstacles that easily lead to an incomplete or incorrect implementation.
43 In this blog-series, we will circumnavigate these obstacles together step by step.
44
45 ## The Example Project
46
47 To illustrate our implementation, we will use a simple example-project.
48 It mimics a part of the registration process for an web application:
49 a (very!) simplistic service takes registration orders for new users.
50
51 - Successfull registration requests will return a 201 (Created), that carries the URI, under which the data of the newly registered user can be accessed in the `Location`-header:
52
53 `echo peter | http :8080/users
54   HTTP/1.1 201
55   Content-Length: 0
56   Date: Fri, 05 Feb 2021 14:44:51 GMT
57   Location: http://localhost:8080/users/peter
58   `
59 - Requests to registrate an already existing user will result in a 400 (Bad Request):
60
61 `echo peter | http :8080/users
62   HTTP/1.1 400
63   Connection: close
64   Content-Length: 0
65   Date: Fri, 05 Feb 2021 14:44:53 GMT
66   `
67 - Successfully registrated users can be listed:
68   `http :8080/users
69   HTTP/1.1 200
70   Content-Type: application/json;charset=UTF-8
71   Date: Fri, 05 Feb 2021 14:53:59 GMT
72   Transfer-Encoding: chunked
73   [
74       {
75           "created": "2021-02-05T10:38:32.301",
76           "loggedIn": false,
77           "username": "peter"
78       },
79       ...
80   ]
81   `
82
83 ## The Messaging Use-Case
84
85 As our messaging use-case imagine, that there has to happen several processes after a successful registration of a new user.
86 This may be the generation of an invoice, some business analytics or any other lengthy process that is best carried out asynchronously.
87 Hence, we have to generate an event, that informs the responsible services about new registrations.
88
89 Obviously, these events should only be generated, if the registration is completed successfully.
90 The event must not be fired, if the registration is rejected, because a duplicate username.
91
92 On the other hand, the publication of the event must happen reliably, because otherwise, the new might not be charged for the services, we offer...
93
94 ## The Transaction
95
96 The users are stored in a database and the creation of a new user happens in a transaction.
97 A "brilliant" colleague came up with the idea, to trigger an `IncorrectResultSizeDataAccessException` to detect duplicate usernames:
98
99 `User user = new User(username);
100 repository.save(user);
101 // Triggers an Exception, if more than one entry is found
102 repository.findByUsername(username);
103 `
104
105 The query for the user by its names triggers an `IncorrectResultSizeDataAccessException`, if more than one entry is found.
106 The uncaught exception will mark the transaction for rollback, hence, canceling the requested registration.
107 The 400-response is then generated by a corresponding `ExceptionHandler`:
108
109 `@ExceptionHandler
110 public ResponseEntity incorrectResultSizeDataAccessException(
111     IncorrectResultSizeDataAccessException e)
112 {
113   LOG.info("User already exists!");
114   return ResponseEntity.badRequest().build();
115 }
116 `
117
118 Please do not code this at home...
119
120 But his weired implementation perfectly illustrates the requirements for our messaging use-case:
121 The user is written into the database.
122 But the registration is not successfully completed until the transaction is commited.
123 If the transaction is rolled back, no message must be send, because no new user was registered.
124
125 ## Decoupling with Springs EventPublisher
126
127 In the example implementation I am using an `EventPublisher` to decouple the business logic from the implementation of the messaging.
128 The controller publishes an event, when a new user is registered:
129
130 `publisher.publishEvent(new UserEvent(this, usernam));
131 `
132
133 A listener annotated with `@TransactionalEventListener` receives the events and handles the messaging:
134
135 `@TransactionalEventListener
136 public void onUserEvent(UserEvent event)
137 {
138     // Sending the message happens here...
139 }
140 `
141
142 In non-critical use-cases, it might be sufficient to actually send the message to Kafka right here.
143 Spring ensures, that the message of the listener is only called, if the transaction completes successfully.
144 But in the case of a failure this naive implementation can loose messages.
145 If the application crashes, after the transaction has completed, but before the message could be send, the event would be lost.
146
147 In the following blog posts, we will step by step implement a solution based on the Outbox-Pattern, that can guarantee Exactly-Once semantics for the send messages.
148
149 ## May The Source Be With You!
150
151 The complete source code of the example-project can be cloned here:
152
153 - `git clone /git/demos/spring/data-jdbc`
154 - `git clone https://github.com/juplo/demos-spring-data-jdbc.git`
155
156 It includes a [Setup for Docker Compose](https://github.com/juplo/demos-spring-data-jdbc/blob/master/docker-compose.yml), that can be run without compiling
157 the project. And a runnable [README.sh](https://github.com/juplo/demos-spring-data-jdbc/blob/master/README.sh), that compiles and run the application and illustrates the example.