Conf42 Enterprise Software 2021 - Online

Demystifying Event-Driven Architectures with Apache Kafka

Video size:

Abstract

What makes a system an “Event-Driven” one? Adding a Message Broker ?! Using FaaS ( Function-as-a-Service ) ?! Or maybe, using Reactive APIs ?! Can anything be modeled to an Event-Driven Architecture? Join my sessions to find out the answer to these questions and many more others.

Summary

  • Bogdan Sucaciu: Together we'll demystify event driven architectures with Apache Kafka. Only a couple of use cases really require event architectures. Most of them are related to real time analytics, fraud detection, sensor readings. But Kafka is an important piece of technology in the event driven world.
  • Demonolit is one of the most powerful architecture patterns. Each microservice gets a different piece of the business domain. This way separate teams can work on separate applications. By using microservices, we now have to solve a different set of problems related to data access.
  • An event driven architecture promotes production, consumption and reaction of events. Producers send data to an event streams and consumers retrieve it from the same event stream. An event stream can be represented by many things, such as logs, topics, queues, websockets or even reactive APIs.
  • In Apache Kafka we have a producer and a consumer. There are two types of events that a producer can send. Events represent actions that have happened or have been triggered sometime in the past. The producer doesn't care about getting back a response from its consumers.
  • The next type of event is event carried state transfer. It is all about the changes that occur in the current state. All consuming parties should avoid querying back to the source system.
  • One other problem that we frequently encounter while using microservices is the distributed source of truth. Our data doesn't reside in a single place, but it is distributed across multiple data stores. How can we make sure the data is in sync?
  • Event sourcing works by always appending new records to the log instead of updating an existing row in a relational database. Kafka consumers also have the ability to rewind and consume events from the past. That's why it is such a powerful option when it comes to event driven architectures.
  • Step of all of this I would like to thank you for joining this session. Feel free to connect with me on Twitter or LinkedIn. I'm always open for interesting discussion. Have fun.

Transcript

This transcript was autogenerated. To make changes, submit a PR.
You event driven architectures problem do they actually solve? Hi and welcome to Conf 42. I'm Bogdan Sucaciu and together we'll demystify event driven architectures with Apache Kafka. I've been working with event driven architectures a couple of years now. Currently I'm a tech lead at Deloitte Digital, a digital consulting firm. And probably some of you may know me from plural website where I teach about various subjects, event driven systems being one of them. Speaking about event driven, this is the subject that we're going to be focusing on today. My goal is rather simple, to change your perspective about it. For a long time they have been marketed as the solution for real time or near real time systems. But is it true though? I mean, we already have real time systems that are not based on events. We create a new user, we make can order, and so on. It's not like I'm going to create a new user, go and grab a coffee, and then 5 minutes later I can come back and finish my order. In fact, only a couple of use cases really require event architectures, and most of them are related to real time analytics, fraud detection, sensor readings and so on. But still, event driven architectures, something else to the table, something much more powerful than you expected. And to illustrate all of this, I'm going to use Apache Kafka. Actually, I'm not going to be using on Kafka that much. If you've already used Kafka, there is quite a big chance that you won't learn anything new today. But still, Kafka is an important piece of technology in the event driven world. Why? Well, I'm going to drop some clues during the session regarding why Kafka makes such a great option for event driven architectures. Without further delay, let's get started. And what better way to start with than one of the most powerful architecture patterns? Demonolit. Yeah, you heard me right. Demonolit is indeed an extremely powerful pattern due to its simplicity. Basically, we have a bunch of lines of code, and all that code is running on a single process. Also, we often have a database attached to it in order to store various data. Most of the time is some sort of a relational database. However, it has quite a major drawback, scalability. And I'm not talking just from an operations perspective like virtually scaling a process, but I'm also talking from a team perspective. I think we've all been there endless conversations on collaboration apps about basically changing the color of a button to pink. So working with monolith is not really scalable or fun. So how can we fix this problem? Well, some very smart people came with the idea, what if we take this big application and split it into multiple services? It first started with SoA service oriented architecture, but it was quite tricky to determine the right bounds for the applications. So this tendency naturally evolved into what we call today microservices. Each microservice gets a different piece of the business domain. This way separate teams can work on separate applications, allowing to scale down or up the number of teams working on these product. Exactly the same concept can be applied to infrastructure. We can scale down or up the number of instances only for the applications we need to. However, there is a slight concerning issue. As we all know, in software engineering we don't have good or bad, right or wrong, we have tradeoffs. So by using microservices, we now have to solve a different set of problems related to data access. A lot of times microservices sound very utopic, like each microservice can evolve independently without having the need to coordinate with another microservice. But I think we've all been there. Practice doesn't transpose 100% from theory. Most of the time we're using synchronous APIs such as rest or RPC calls to exchange data between our services. Let's say that one of our users makes a request to one of our services. From there we have an entire cascade of other calls that need to be done from one single rest call. We may end up with 20 or even 30 other calls. This happens because even if each microservice owns a single piece of the business domain, we will always need data from other domains to get the full picture. Let's say we want to make a payment transaction. It's not like we only need the credit card details. No, we also need the name of our user or even his address. And that is usually stored by a different microservice. So in order to get the full picture, we always need to somehow compose it by calling different microservices from our architectures. Sounds easy, but by doing this we are diving into a new set of challenges, such as distributed transactions, timeouts, dependency management, coupling or even data ownership. These challenges and many more others have been introduced by microservices and by the fact that we are now using distributed systems to perform our tasks. And that's what event event driven architectures really good at. They can easily solve distributed systems problems by introducing new concepts and patterns. But first, what exactly is an event driven architectures this is these main talking point of my presentation and I haven't even introduced it yet. Well, I was curious too, so I went to Wikipedia and I found this event. Event driven architectures shortly said EDA is a software architecture paradigm promoting production, consumption of and reaction of events. There are some interesting words being thrown in there, production, consumption, reaction and so on. So let's find out what they actually mean. To do that, we need to go back to our microservices to keep things simple. I'm going to pick only four services, blue, gray, purple and red. To make things interesting. The blue service is using reSt APIs to communicate with the purple and red services, and an RPC call to communicate with the gray service. Rest APIs are based on a request response type of communication, whereas with RPC calls we don't always get back a response. This flow can also be represented in another way using unidirectional flow of data. Now the blue service calls the gray service and the flow ends. But when the blue service calls the purples and the red services, the flow continues and they will provide some sort of data back to the blue service. This is interesting, but we still haven't solved any of the problems mentioned earlier. To solve them, we need another piece of the puzzle called event stream. The trick is quite simple. We now have two types of applications, producers and consumers. Producers send data to an event streams and consumers retrieve it from the same event stream. What's interesting is the fact that an application can be both a producer and a consumer at the same time. Just like the purple and the red services, these consume data from the blue event stream and produce it into another. Now, if we isolate only the producers, we can immediately notice that there is only one producer type per event stream. The blue service produces on the blue event stream, the purple service produces on the purple event stream, and so on. You should definitely keep this in mind when you're designing your event driven architectures. You could, for example, use multiple producer types to produce to one event stream, but that will complicate a lot. The consuming process. Then if we take a look over the consuming part, we can notice that multiple consumers can retrieve data from the same event stream. Also, one consumer is allowed to consume data from multiple event streams. So consumers are much more flexible than producers in terms of connection to different event streams. And by introducing only one concept, we actually tackled multiple issues. First of all, our applications are no longer coupled since the event stream is meant to serve as a decoupling factor and then data ownership is already simplified. Each event stream stores one type of data and any interesting party can easily consume it. But what exactly is an event stream? Well, an event stream can be represented by many things, such as logs, topics, queues, websockets or even reactive APIs. Now I know that some of you may be thinking, hey, how can a websocket be considered an event stream? Well, if you come to think of it, websockets or even reactive APIs are a bit more special scenario. The event stream and a producing application have merged together into a single entity. But if you think about the previous deductions, we still have only one producing application, and multiple consumers can connect to the same event stream. The only difference between a reactive API and a topic is that the event stream and a producing application are two separate entities. In fact, anything that you can asynchronously subscribe to can be can event stream. Any tool, library or protocol that can produce data in an asynchronous manner can be considered an event stream. These are, however, some fundamental differences between these event streams types. For example, queues, websockets and reactive APIs treat event differently compared to topics and logs. While using the first category, we can only react to events, we consume them and then they are gone with no option of getting them back. Topics and logs, on the other hand, can persist those events and we can replay them later on. This pattern is a bit more powerful and we'll see why a bit later. An important thing to remember is that kafka uses topics as event streams, and under the hood it uses logs to persist events. This is one of the main reasons Kafka is such can excellent choice for building event driven applications. Of course, Kafka is not the only system that allows this. These are plenty of other examples such as AWS kinesis or Apache Pulser, which work in a similar fashion. But Kafka has another ace under its sleeve, something that kinesis and pulser aren't that good at, which we'll see in just a few minutes. So in Kafka's world, event streams are represented by Kafka topics residing on Kafka brokers producers connect to the Kafka broker in order to produce data, and consumers have to do the same thing in order to consume it. This brings some implications to our way. We exchange data throughout the system. Firstly, we're now dealing with something called inversion of control, and it's quite a simple pattern. While using the classic rest API approach, it is the job of the producer to send these data to the consumer. Now, the producer doesn't care anymore. Its only job is to produce data to an event stream. Actually, it's the consumer's job to retrieve the data by subscribing to the event stream. So we are inverting the control from the producer to the consumer. By empowering the consumer to retrieve the data that it is interested in, then we have can out behavior. Multiple consumers can consume data produced by only one producer. Talking about producers, it is recommended to have only one producer type per event stream. Just to make things clear, I'm referring to the producer type and not producer instance. Multiple producer instances can produce to the same event stream as long as they are on the same type. And finally, producers don't really know consumers. From an application perspective, a producer is like hey, my only job is to produce data to this event stream, but I don't know who is going to consume it and frankly, I don't even care. Consumers have the same mindset. These don't know who is producing this data, they only care about their connection to the event stream. However, if we zoom out a bit from an architecture perspective, consumers and producers are well known in order to get the full picture of the business flow. So going back to microservices, our main challenge was data access. By adopting an event driven architectures, we're actually solving it. Our data is now really easy to access. We just have to make our consumers subscribe to an event stream and that's it. It's much easier to subscribe to one or more event streams rather than making 20 or 30 API calls. But still, just as I mentioned previously, in software engineering, we don't have right or wrong, we have tradeoffs. And the tradeoff for event architectures is consistency. While using synchronous APIs we have that strong consistency, and we're always sure that we're getting the latest version of our data in event driven architectures no longer have that. Instead, we have something called eventual consistency. In fact, the key to building successful event driven architecture is understanding and accepting eventual consistency. We can't just simply take a synchronous rest API and then transpose it in an event driven way. New patterns require new ways of thinking, so such attempts may fail due to the mismatch between these patterns. In fact, not all flows are meant to be event driven. In the real world, we will always find a mix between synchronous APIs and eventing patterns. Now, I've babbled a lot about events, but I haven't really said what an event really is. Well, to do that we need to have a look of our communication types between systems and the best way to start with is a message. A message is just some data being exchanged between two different services a and b. A message has a form, a body, but it doesn't say how the data is being transferred. That's why we need some more concrete definitions. The types of messages can be determined based on when these action resulted from the data transfer is happening. The first one is command a. Command will always happen sometime in the future. Even if we initiate the action now, the result of it will be perceived sometime in the future. Also, a command is a directed instruction, meaning that we know exactly who we are communicating with. An example will be rest calls service a, calls service b. A very important thing to know is that the fact that we don't always receive a response while using the command pattern. Sometimes we do, sometimes we don't. The opposite of a command is the query. Now, our service does not give instructions to another, but it actually requests some data, just like trying to request a database or a search engine. Queries happen in the present because we are requesting the current state of our data. Also, one important fact is that we are always getting a response back. Finally, we have events. Events are a bit different compared to the other two. They represent actions that have happened or have been triggered sometime in the past. As you've probably noticed, commands and queries require point to point communication service a, query service b, while events on the other hand are undirected, meaning that anyone can consume events without the producer knowing. Also can event producer will never receive a response from its consumers. In fact, there are two types of events that a producer can send. The first one is called event notification. And just like his name is suggesting the event producer is informing its consumers that an action has happened. I can actually give you a great example of this pattern in practice. I'm not really sure if I can mention their name, but let's say that one of the biggest ecommerce companies has an extremely interesting use case on their homepage. They are displaying some random items that you may or may not be interesting in buying them. However, at the bottom of the page they also have a questions section. Do you know how they are compiling those suggestions? Well, we tend to move the cursor based on the direction of our eyesight. If we look to the right, we also move the cursor to the right. So if we see something that may attract us on that page, we tend to hover these cursor over that specific item. The moment we do that can event is being sent to their questions service notifying that we have hovered over that product. So as we scroll down the page we probably hover over a couple of items. Then at the bottom of the page we are getting suggestions based on which items we have hovered over. Sounds really cool, right? This is actually one of the best examples of the event notification pattern because it really denotes that eventing aspect. The producer isn't interested in getting back a response, and it also doesn't care about its consumers. All the producer does it cares about is to send hover events to the backend system. So how would something like this would look in Apache Kafka? Well, we always have at least two applications, a producer and at least one consumer. The first step is to define the event stream, in our case a Kafka topic. By the way, I'm using Java here, but there are kafka clients that would work with many other programming languages like Golang, Python and so on. Then in the next section we have to define some configuration. Both our producer and consumer need to know how to connect to the Kafka cluster and what serialization format should use. Next we have to initialize a Kafka producer and a Kafka consumer. Pretty straightforward so far. Now things start to divert a bit. The producer has to create a producer record. You can consider a record as an equivalent to an event. It's just that record is the official naming in Apache Kafka. A quick fact about records, a record is composed of a key and a value. Keys are used to identify events and values store the payload of that event. On the other hand, these consumer has to subscribe to the same topic the producer is producing too. We can actually pass a collection of topics meaning that our consumers can subscribe to multiple event streams at the same time. Finally, these only thing left to do for our producer is to send that event to the Kafka cluster. I've talked earlier about inversion of control and how it's the consumer job to actually make sure it retrieves the data well. The way this works in Kafka is by using these poll pattern. The consumer will actively query these Kafka cluster in order to find if there are new events to be consumed. As soon as the poll method returns, we are free to do whatever we want with those events. However, the poll action only happens once. That's why we need to wrap it in a while. True loop the end result sounds like a broken record. Hey, do you have new events for me? Do you have new events for me? Do you have new events for me? If the broker has new events in the event stream, it will pass them further to the consumer. Let's actually see how these code would look in action. So I have four terminal windows. On the left I will be running a single producer. On the right I will be running not one but three different consumers. All three will do exactly these same thing. They will lock the consumed event. In practice, these would actually consume the events in different ways. Starting with a producer, we can see that it's producing messages containing an incremental number as the key and a random uid as the value. I've used a while loop to make sure that an event is generated every 1 second. Now let's start the consumers. Firstly consumer one, then consumer two, and finally consumer three. As you can notice, all three of them are receiving the same events almost at the exact same time. Just try to think about how much code it would take to write an application that does the same thing using synchronous APIs. Of course we would get some added benefits like strong consistency, but if we don't really need them, it's fine. The next type of event is event carried state transfer. I know it sounds quite fancy, but I think it will be quite easy to explain it using an example. Event carried state transfer is all about the changes that occur in the current state. Let's take for example, the address changed event. A user changes his or her address through a user interface. This is a change in the state. Now our producer needs to send the address changed event to the event stream. But how much information should that event contain? It should contain only the new address, the old one and the new one. What about the user information? Let's say we want to go minimal and pick only the new address. That event is produced to Kafka, which is then picked up by the consumers. Only then we realize that one of the consuming parties is a tax service that requires the oral address as well. Uhoh, what can we do in this case? Well, that service can query back to the source system and get the missing data. Do you notice the problem? Because we haven't passed enough information into our event, we have now introduced coupling between our services. That's why when you're designing your event model, take into consideration that all consuming parties should avoid querying back the source system. One other problem that we frequently encounter while using microservices is the distributed source of truth. Our data doesn't reside in a single place, but it is distributed across multiple data stores. So in order to compute the results, we actually need to query multiple databases, and it's quite tricky to do it the right way. Sometimes we may be tempted to apply some workarounds, like copying parts of the data from one database to another. But this also poses some challenges. How can we make sure the data is in sync? How can we make sure that the moment we compile our result, we have the latest version of the data? A lot of effort would have to go into writing, deploying and maintaining some database synchronizers. We are now working with distributed systems, so we need another way to tackle this, a more efficient one. And these is these event sourcing comes into play event sourcing is probably one of the most powerful patterns, but it is also one of the most demanding. It all comes down to an event log. You can think of an event log as a ledger where we keep various actions that have happened. For example, let's think of a bank account. When we open that bank account, we start with the initial amount zero. Then money is being deposited into that account. First we have a transaction of 500 and these another transaction of 200. Finally, we have to pay for that new tv and $300 are subtracted from that account. So event sourcing works by always appending new records to the log instead of updating an existing row in a relational database. If we want to compute the current amount, the only thing we have to do is to replay the log and go through all the transactions. By doing so, we can easily obtain the current balance of our bank account, which in our case would be $400. These is how event sourcing works. We always build the current state by replaying all the events from the log. You may think that these pattern is rather new, but we're actually using it for quite some time now. To give you an example, Git is actually an event sourcing system. We always retrieve the latest version of our code base by replaying commits from the log. So why is this pattern so powerful? Well, it solves our data access problem. Let's say that we have three applications that need some data. In the Kafka world, these would be some simple consumers that subscribe to a Kafka topic. Just a quick reminder. Kafka uses log files to store events. That's why it is such a powerful option when it comes to event driven architectures. Cool. Now let's say that two events are produced to the event log. The only thing the consumers would have to do is to consume those two events. They can be stored either in memory or any persistence layer. By doing so, we are building a socalled materialized view. We are building a current state based on events. If a new event is being produced, the consumers will pick it up and update their materialized views. But this doesn't happen in an instant. There's a slight delay between actually appending the event in the log and updating these materialized view. That's why we have to accept eventual consistency. We know that at some point in time our data will be consistent, but we don't know exactly when. Now, not everything goes perfect in production and things may fail. Let's say that one of our data stores goes down and we lose all our data. But that is totally fine. Why? Because all the other applications are not going to be impacted. If new events are being produced, the other applications can consume them in their own rhythm and update their materialized views. But what about the application that went down? Well, we can simply bring back an empty data store. Kafka consumers also have the ability to rewind and consume events from the past. That means we can start consuming from the beginning of these event log and recreate the current state. I always like to end something on a positive note. So what I want to say is that the game is on. We are actually witnessing a transition period and we are all part of it. A couple of years ago microservices were really popular and everyone was talking about them. Right now the same thing happens with event driven system we are changing our systems to adapt to a new mindset and event event event driven architectures. Step of all of this I would like to thank you for joining this session and don't forget I'm available to answer any questions that you may have. Feel free to connect with me on Twitter or LinkedIn. I'm always open for interesting discussion. Thank you again and I hope I'll see you next time. Have fun.
...

Bogdan Sucaciu

Tech Lead @ Deloitte Digital

Bogdan Sucaciu's LinkedIn account Bogdan Sucaciu's twitter account



Awesome tech events for

Priority access to all content

Video hallway track

Community chat

Exclusive promotions and giveaways