144
CHAPTER 5 | Designing and Developing Multi-Container and Microservice-Based .NET Applications
means storing only domain events in your transactional database, instead of storing current state
data. Storing only domain events can have great benefits, such as having the history of your system
available and being able to determine the state of your system at any moment in the past. However,
implementing a full ES system requires you to rearchitect most of your system and introduces many
other complexities and requirements. For example, you would want to use a
database specifically
made for event sourcing, such as
Event Store
, or a document-oriented database such as Azure
Cosmos DB, MongoDB, Cassandra, CouchDB, or RavenDB. ES is a great approach for this problem, but
not the easiest solution unless you are already familiar with event sourcing.
The option to use transaction log mining initially looks transparent. However, to use this approach,
the microservice has to be coupled to your RDBMS transaction log, such as the SQL Server transaction
log. This approach is probably not desirable. Another drawback is that the low-level updates recorded
in the transaction log might not be at the same level as your high-level integration events. If so, the
process of reverse-engineering those transaction log operations can be difficult.
A balanced approach is a mix of a transactional database table and a simplified ES pattern. You can
use a state such as “ready to publish the event,” which you set in the original event when you commit
it to the integration events table. You then try to publish the event to the event bus.
If the publish-
event action succeeds, you start another transaction in the origin service and move the state from
“ready to publish the event” to “event already published.”
If the publish-event action in the event bus fails, the data still will not be inconsistent within the origin
microservice
—it is still marked as “ready to publish the event,” and with respect to the rest of the
services, it will eventually be consistent. You can always have background jobs checking the state of
the transactions or integration events. If the job finds an event in the “ready to publish
the event”
state, it can try to republish that event to the event bus.
Notice that with this approach, you are persisting only the integration events for each origin
microservice, and only the events that you want to communicate to other microservices or external
systems.
In contrast, in a full ES system, you store all domain events as well.
Therefore, this balanced approach is a simplified ES system. You need a list of integration events with
their current state (“ready to publish” versus “published”). Bu
t you only need to implement these
states for the integration events. And in this approach, you do not need to store all your domain data
as events in the transactional database, as you would in a full ES system.
If you are already using a relational database, you can use a transactional table to store integration
events. To achieve atomicity in your application, you use a two-step process based on local
transactions. Basically, you have an IntegrationEvent table in the same database where you have your
domain entities. That table works as an insurance for achieving atomicity so that you include persisted
integration events into the same transactions that are committing your domain data.
Step by step, the process goes like this:
1.
The application begins a local database transaction.
2.
It then updates the state of your domain entities and inserts an event into the integration event
table.
3.
Finally, it commits the transaction, so you get the desired atomicity and then
145
CHAPTER 5 | Designing and Developing Multi-Container and Microservice-Based .NET Applications
4.
You publish the event somehow (next).
When implementing the steps of publishing the events, you have these choices:
•
Publish the integration event right after committing the transaction and use another local
transaction to mark the events in the table as being published. Then, use the table just as an
artifact to track the integration events in case of issues in the remote microservices, and perform
compensatory actions based on the stored integration events.
•
Use the table as a kind of queue. A separate application thread
or process queries the
integration event table, publishes the events to the event bus, and then uses a local transaction
to mark the events as published.
Figure 6-22 shows the architecture for the first of these approaches.
Figure 6-22. Atomicity when publishing events to the event bus
The approach illustrated in Figure 6-22 is missing an additional worker microservice that is in charge
of checking and confirming the success of the published integration events. In case of failure, that
additional checker worker microservice can read events from the table and republish them, that is,
repeat step number 2.
About the second approach: you use the EventLog table as a queue and always use a worker
microservice to publish the messages. In that case, the process is like that shown in Figure 6-23. This
shows
an additional microservice, and the table is the single source when publishing events.
146
CHAPTER 5 | Designing and Developing Multi-Container and Microservice-Based .NET Applications
Figure 6-23. Atomicity when publishing events to the event bus with a worker microservice
For simplicity, the eShopOnContainers sample uses the first approach (with no additional processes or
checker microservices) plus the event bus. However, the eShopOnContainers sample is not handling
all possible failure cases. In a real application deployed to the cloud, you must embrace the fact that
issues will arise eventually, and you must implement that check and resend logic. Using the table as a
queue can be more effective than the first approach if you have that table as a single source of events
when publishing them (with the worker) through the event bus.
Dostları ilə paylaş: