‫Welcome back.
‫In this lecture, we're going to learn
‫about another architectural style and pattern,
‫which is called Event-Driven Architecture.
‫First, we will get an introduction
‫to what Event-Driven Architecture is.
‫And later, we will learn about all its benefits,
‫as well as use cases where this type
‫of architecture can be very powerful and useful.
‫So what is an Event-Driven Architecture
‫and what are its main components?
‫If we recall from the lecture about microservices,
‫if microservice A wants to communicate with microservice B,
‫then not only microservice A needs to be aware
‫of the existence of microservice B
‫but also it needs to know what API microservice B provides
‫and how to call it.
‫Also, at runtime,
‫microservice A has to call microservice B synchronously
‫and wait for its response.
‫So from what we just described,
‫we can see that, essentially,
‫microservice A has a dependency on microservice B.
‫In an Event-Driven Architecture,
‫instead of direct messages that issue commands
‫or requests that ask for data,
‫we have only events.
‫An event is an immutable statement of a fact or a change.
‫For example, a user clicking on a digital ad
‫or an item being added to a shopping cart can be thought of
‫as fact events
‫while a player of a video game or an IoT device,
‫like a vacuum cleaner,
‫moving from one position to another can be thought of
‫as change events.
‫In an Event-Driven Architecture,
‫we have three components.
‫On the sending side,
‫we have the event emitters,
‫which are also referred to as producers.
‫On the receiving end,
‫we have the event consumers.
‫And in between,
‫we have the event channel,
‫which is essentially the message broker we learned about
‫in a past lecture.
‫When we use the Event-Driven Architecture style
‫with microservices, we can get a lot
‫of very useful properties and benefits.
‫For example, now, if microservice A communicates
‫with microservice B by producing events,
‫the dependency of microservice A
‫on microservice B is removed.
‫In fact, microservice A doesn't need to know anything
‫about the existence of microservice B.
‫And once microservice A produces the event,
‫it doesn't need to wait for any response from any consumer.
‫Because services don't need to know
‫about each other's existence or API
‫and all the messages are exchanged
‫completely asynchronously, we can decouple microservices
‫more effectively, which in turn provides our system
‫with higher scalability.
‫And we can add more services to the system
‫without making any changes.
‫For example, if we have a banking system,
‫we can start with just two services.
‫The first service is the front-end service,
‫which simply provides the user with the user interface
‫and takes in the user's input,
‫such as money deposits,
‫transfers between accounts, and so on.
‫The second service is the account service,
‫which maintains and updates the balance for each user.
‫Now, if we use an Event-Driven Architecture,
‫then every action that the user performs
‫on their account can be events that are produced
‫by the front-end service.
‫And the account service simply subscribes to those events
‫so it can consume them and update the user's balance.
‫Now, because we decouple those services using
‫a message broker,
‫we can easily add a mobile notification service,
‫which subscribes to the same channel.
‫And every time a user makes changes to their account,
‫the service can send push notifications
‫to the user's mobile device about the activity
‫in their account.
‫Notice that adding the service was super easy
‫and did not require us to make any changes
‫to the front-end service.
‫Later, we can just as easily add a Fraud Detection Service,
‫again, without making any changes to the existing services.
‫And similarly, we can add another producer service
‫that can integrate with other third-party services.
‫For example, it can integrate with utility companies
‫that charge our clients automatically for gas,
‫electricity, or water.
‫Or we can integrate with payroll services
‫that perform direct deposits to the client's account
‫on behalf of the client's employer.
‫And adding this producer will also not require us
‫to make any changes to our system.
‫In addition to the horizontal
‫and organizational scalability,
‫Event-Driven Architecture allows us
‫to analyze streams of data,
‫detect patterns, and act upon them in real time.
‫For example, the Fraud Detection Service can detect
‫suspicious activity in the user's account
‫by looking at the stream of events
‫that happen in the account in real time
‫without waiting for this data to be post-processed
‫and stored in some database.
‫For example, it can look at the recent transactions,
‫and notice that in the last hour,
‫two transactions happened in stores and restaurants
‫in Los Angeles, California
‫while one of the transactions happened in a remote location
‫somewhere in a different state.
‫The Fraud Detection Service can easily detect
‫the suspicious activity,
‫which is possibly a result
‫of someone stealing our user's credit card
‫or account information and trying to make a purchase.
‫Similarly, by analyzing a stream
‫of transactions in real time,
‫it can detect that five different transactions were made
‫within a short amount of time,
‫which simply cannot correlate with real-human activity.
‫This activity would be immediately flagged as fraudulent
‫and would trigger the Fraud Detection Service
‫to communicate with the account service,
‫which would freeze the user's account.
‫And it would also communicate with the notification service,
‫which would alert the user about the situation.
‫When we store all the events that happen in our system
‫inside a message broker
‫besides performing real-time analysis,
‫we can also use this information
‫to implement very powerful architectural patterns.
‫One of those architectural patterns
‫is called event sourcing.
‫If we continue with the same example of the banking system
‫and imagine what a hypothetical log of events
‫for a particular user would look like,
‫we can notice that, essentially,
‫this event log represents all the transactions
‫that happened in the user's account.
‫And if we replay all those transactions
‫from the beginning of time,
‫we can arrive at the account balance
‫that is currently stored in the account service database.
‫So by using the event sourcing pattern
‫instead of storing the current state in a database,
‫we can simply store only the events,
‫which can be replayed whenever we need to know
‫the current state,
‫and we eliminate the need for the database.
‫Now, because events are immutable,
‫we never modify them.
‫And we simply append new events to the log as they come.
‫By using this pattern,
‫we can add another service that can generate a statement
‫or look up any number of transactions
‫that happened in our user's account
‫simply by looking at the last N events in the log.
‫And if our Fraud Detection Service decided
‫that one of those transactions was not approved
‫by the customer,
‫it can easily be fixed by adding another event
‫into the log that compensates for it,
‫and all of that without the need to freeze
‫the user's account or modifying records in a database.
‫For example, if the user was charged $100
‫by somebody who stole the user's credit card information,
‫the Fraud Detection Service can simply credit
‫the user's account with $100.
‫And all of that information will be reflected
‫in the user's statement immediately.
‫Using the event sourcing pattern,
‫we can choose to store those events
‫for as long as we want
‫and allow the user to look back at his transactions
‫from 5 or even 10 years ago.
‫But we can also make our querying faster
‫by adding snapshot events,
‫let's say, every month that summarizes
‫everything that happened until that point of time.
‫Another very powerful architectural pattern
‫that we can implement using Event-Driven Architecture
‫is called CQRS.
‫CQRS stands for command query responsibility segregation.
‫This pattern can solve two problems for us.
‫The first problem is optimizing a database
‫that has a high load of both Read and Update operations.
‫When we have a database that has a high load
‫of both Read and Update operations,
‫concurrent operations to the same records
‫or tables contend with each other,
‫making all the operations very slow.
‫Additionally, if we use a distributed database,
‫generally, we can optimize it only for one type of operation
‫at the expense of the other.
‫For example, if we have a read-intensive workload,
‫we can compromise on slower writes,
‫or if we have our write-intensive workload,
‫we can compromise on the performance of Read operations.
‫However, in the case
‫when both operations are equally frequent,
‫we have a problem.
‫The CQRS architectural pattern allows us
‫to separate Update and Read operations
‫into separate databases,
‫sitting behind two separate services.
‫In this case,
‫service A would take all the Update operations
‫and perform those updates in its own database,
‫where it optimally stores the data for such updates.
‫But additionally, every time
‫an Update operation is performed,
‫it also publishes an event into a message broker.
‫Meanwhile, service B subscribes to those update events
‫and applies all those changes
‫in its own read optimized database.
‫And now all the Read operations will only go to service B.
‫Now both Update and Read operations can go
‫to two separate services
‫without interfering with each other.
‫And the data in each service is stored in an optimized way
‫for each type of operation.
‫The second problem
‫that CQRS architectural pattern helps us solve
‫is joining multiple tables that are located
‫in separate databases that belong
‫to different microservices.
‫Before we split our monolithic application
‫into microservices, we had all the data tables
‫inside one single database.
‫If that was a relational database,
‫we could easily and relatively quickly combine
‫and analyze records from multiple tables
‫by simply using the SQL Join operation.
‫However, once we migrate to microservices architecture
‫and follow the best practice
‫of having a separate database for each microservice,
‫those Join operations are a lot harder.
‫Now we need to send a request to each service separately,
‫which is a lot slower.
‫And then we also need to combine this data programmatically
‫because now we potentially have
‫different types of databases,
‫some of which may not even be relational databases.
‫So CQRS solves exactly that problem.
‫Now, every time there is a change
‫in the data stored in service A or service B databases,
‫those services would also publish those changes as events
‫to which service C subscribes.
‫Meanwhile, service C stores
‫what's called a materialized view
‫of the joined ready-to-query data from both service A
‫and service B in its own read-only database.
‫And now, whenever we need to get a join view,
‫we don't need to send a request to two different services.
‫Instead, we need to send a request to service C only,
‫which already has the data ready for us.
‫Now let's demonstrate both of those use cases
‫for CQRS pattern in a real-life example.
‫Let's assume that we have an online store
‫where we have hundreds of thousands of products
‫and we have millions of users who search for
‫and leave reviews for those products daily.
‫So if we use the microservices architecture,
‫we would have the product service that has its own database.
‫That database contains things like the product's name,
‫their inventory count,
‫description, price, and so on.
‫We would also have the review service,
‫which manages and stores all the reviews
‫for all those products.
‫So as we can imagine,
‫reviews are coming to our system constantly
‫so updates to the reviews database are very frequent.
‫Similarly, our product's inventory keeps changing,
‫as people purchase products
‫or additional products are added and updated.
‫But we also need to read
‫and combine the product's information
‫and their reviews information very quickly and frequently
‫because users constantly search for products
‫and want to see both their description and prices,
‫which would come from the product service,
‫and also see each product's reviews and rating,
‫which we would need to get from the review service.
‫So to solve this problem,
‫we can use the CQRS architectural pattern.
‫By using this pattern,
‫we can add the product search service,
‫which would have its own database.
‫This database would store all the necessary combined data
‫for each product and its reviews.
‫And the service would also subscribe to updates to this data
‫from the review service and the product service.
‫So now a user who wants to search for a vacuum cleaner
‫or maybe a pair of shoes can get a page
‫with all the potential results very quickly
‫by sending a request only to the product search service.
‫And if the user wants to sort by
‫or filter by the number of reviews or rating,
‫it can also be done very easily and quickly.
‫In this lecture,
‫we learned about the Event-Driven Architecture.
‫We learned about a few benefits
‫that this type of architecture provides to us,
‫especially when combined
‫with the microservices architecture.
‫A few of those benefits include decoupling microservices,
‫which provides us with higher horizontal
‫and organizational scalability.
‫Additionally, Event-Driven Architecture allows us to analyze
‫and respond to large streams of data in real time.
‫We also learned about two very useful
‫and powerful architectural patterns
‫within Event-Driven Architecture.
‫The first one was event sourcing,
‫which allowed us to store and audit the current state
‫of a business entity by only appending immutable events
‫and replaying them when we need to.
‫And the second architectural pattern was CQRS,
‫which allowed us to optimize our database
‫for both Update and Read operations
‫by completely splitting the operations
‫to different services.
‫This pattern also allowed us to quickly
‫and efficiently join data
‫from completely different services and databases.
‫I hope you learned a lot in this lecture,
‫and I will see you all very soon.