Extracting the Delivery Service - Step 3: define standalone
The previous post described the second step of the refactoring process, which consisted of splitting the
ftgo database schema and defining an
The third step of the refactoring process is to define a standalone
Delivery Service and deploy it.
The following diagram shows the design:
Delivery Service’s API consists of:
- A REST API, which is implemented by the
- A request/asynchronous response-style API, which is implemented by
DeliveryServiceCommandHandlers, that will eventuallybe used by the FTGO monolith for scheduling and cancelling deliveries
- An event publishing API, which is implemented by
DeliveryEventPublisher, that publishes events when a courier’s schedule has been updated. It’s used instead of database triggers to replicate updates to courier’s schedules to the monolith. The
FTGO monolithnow contains a
OrderServiceEventHandlers, which subscribes to the events published by
Delivery Managementand updates its local data.
The following diagram shows the new module structure:
There are the following modules:
ftgo-delivery-backend*- previously these were the
ftgo-delivery-service- a new module, which contains the
Delivery Service’s main class and the
DeliveryServiceCommandHandlers. It reuses the
ftgo-delivery-backendmodule from the monolith rather than implementing the delivery management logic from scratch.
The event-based data integration glue is implemented by classes in the following modules:
ftgo-delivery-backend- contains the
ftgo-order-service- contains the
Since these modules are part of the
FTGO monolith, it uses the same event-based mechanism internally.
Why replace triggers with events?
One benefit of using triggers to synchronize two databases is that it doesn’t require any changes to the code. The synchronization is handled entirely by the database. A significant drawback, however, of using triggers is that it results in tight design-time and runtime coupling between the two databases. There is design-time coupling between the database, because a trigger has knowledge of both databases. There is also runtime coupling since both databases are updated within a single transaction.
Another drawback of using triggers is that the coupling makes it difficult to test the newly extracted service in isolation.
Consider, for example, a test that verifies that the
Delivery Service correctly processes a
ScheduleDelivery command message.
If a trigger replicates updates to a
Courier’s schedule from
ftgo_delivery_service database to the
ftgo database then the test must correctly setup that database as well.
A much better approach is to use events to replicate changes.
Delivery Service can, for example, publish domain events when it updates a
FTGO application subscribes to those events and updates its database.
An event-based replication mechanism has the following benefits:
- Reduced design-time coupling - services communicate via APIs rather than access each other’s implementation details
- Eliminates runtime coupling - there is no longer runtime coupling between the services
- Improved testability - a test can easily verify that a service emits the expected events
It also has a couple of drawbacks. The first drawback of using events is that you need to change the application code to publish and consume events. In particular, modifying all of the places that publish events in a monolithic application might be prohibitively time consuming and error-prone.
The second drawback of using events to replicate changes is that it is eventually consistent.
Unlike triggers, which execute within the ACID transaction that updates the source database, an event handler runs in a separate database transaction
For example, the FTGO application executes the
acceptOrder() command in a single transaction, which accepts the
Order in the
ftgo database, schedules the
Delivery in the
ftgo_delivery_service database, and replicates the changes (via a trigger) back to the
In contrast, the event-based design uses two transactions.
The first transaction accepts the
Order in the
ftgo database, and schedules the
Delivery in the
The second transaction replicates the changes back to the
As a result, the
acceptOrder() command no longer updates the
In the future, the
FTGO application might use events.
But for now, we’ll just change
Delivery Management to publish events it’s a relatively small change that makes it much easier to test the
Delivery Service uses events rather than triggers to replicate changes to the
FTGO application it’s straightforward to test the service in isolation.
DeliveryServiceComponentTest implements a component test for the
The following diagram shows how this test works:
It sets up couriers and restaurants (normally replicated from the
ftgo database via triggers) using JPA.
DeliveryServiceComponentTest then sends a command message to the
Delivery Service and asserts that it publishes the expected events.
FTGO application, the
Delivery Service is a Spring Boot application that is deployed a Docker container.
ftgo-delivery-service/Dockerfile file defines the container image.
docker-compose.yml file defines at
ftgo-delivery-service container along with the infrastructure services needed for transactional messaging: Apache Zookeeper, Apache Kafka, and Eventuate Tram CDC.
These changes are in the
extract-delivery-service-03-define-service branch and consist of the following commits:
- Define the standalone service
- Replaced Service->Monolith triggers with Tram events
- Implemented Component Test for