Sample project implementing CQRS pattern.
In a microservice application, it is difficult to implement a query that retrieves data that spans multiple services.
If you don't have a lot of services involved, it's also a good choice to configure with the API Composition pattern.
It is to define a query in each of the services involved, combine data in one service and return a value.
However, the API Composition pattern has the following disadvantages.
- Increased Overhead
An increase in overhead is unavoidable because multiple services and DBs are called multiple times. As network resource usage increases, operating costs will increase. - Poor Availability
If the three services each have an availability of 99.5%, when all services are combined, the availability is 99.5%^3=98.5%. Availability will be lower if more services are combined. - Lack of Data Consistency
Because ACID transactions are not guaranteed, the data queried by each service may be inconsistent.
Command Query Responsibility Segregation (CQRS), as its name suggests, is the segregation of responsibility for processing queries from the responsibility of processing commands in the system.
That is, the query (R, Http GET) function is implemented in the query-side module, and the create/update/delete (CUD, Http POST PUT DELETE) function is implemented in the command-side module.
Data synchronization between the two modules is typically done in a way that the command-side module issues an event and the query-side module subscribes to the event.
The advantages of applying the CQRS pattern are:
- Efficient queries are available.
Stores pre-joined views of data from multiple services, there is no need for expensive in-memory joins like API Composition pattern. There are also cases where it is difficult or impossible to implement different kinds of queries with only a single data model. The CQRS pattern allows you to overcome the limitations of a single data model (storage) by defining a data view that fits each query. - Separation of interests simplifies the management of each module.
CQRS pattern defines the DB schema for each interest in the query-side module and the command-side module. In addition, the service that implements the query may differ from the service that owns the data.
But there are also disadvantages of CQRS.
- Complex Architecture
- Handling replication lag
The lag between the command-side module and the query-side module must be handled. This is the gap before the event issued by the command-side is consumed and processed by the query-side module. You must ensure that inconsistent data is not exposed to the user as much as possible.
- Create pending order and send a
ORDER_CREATED
event toorder-customer
topic that customer service subscribes to. - Customer service receives an order aggregate and verify the value of
customerId
field is valid. Sends aCUSTOMER_APPROVED
event if it is a valid value, or aCUSTOMER_REJECTED
event if it is not valid. - If a message from
order-reply
topic tellscustomerId
is verified, then send aORDER_CREATED
event toorder-ticket
topic that restaurant service subscribes to. - Restaurant service receives an order aggregate and creates a pending ticket if available. Sends
TICKET_REJECTED
if ticket creation fails,TICKET_CREATED
event if successful. - If a message from
order-reply
topic tells ticket is created successfully, then sendORDER_CREATED
event toorder-payment
topic that payment service subscribes to. - Payment service receives an order aggregate and approve payment if available. Sends
PAYMENT_REJECETED
if payment approval fails,PAYMENT_APPROVED
event if successful.
- If a message from
order-reply
topic tells payment is approved, then order service sendsORDER_APPROVED
event to restaurant service. - Restaurant service receives an order aggregate and change the state of ticket to
ACCEPTED
and sendTICKET_APPROVED
event.
- If a message from
order-reply
topic tells ticket is accepted, then send aORDER_APPROVED
event toorder-kitchen
topic that kitchen service subscribes to. - Kitchen service receives an order aggregate and creates a
PREPARING
cook entity. Sends aKITCHEN_PREPARING
event.
- If a message from
order-reply
topic tells that foods are cooking, change the state of ticket and order toPREPARING
. - State change proceeds in the same way as steps 6 and 7.
- After cooking has been finished, API
/kitchen/ready
will be called and send aKITCHEN_READY
event. - State fields of order and ticket are changed to
READY
and it will proceed in the same way as steps 6 and 7.
- After a delivery man picks up the foods, API
/delivery/pickedup
will be called and sendDELIVERY_PICKEDUP
event.
- State fields of order and ticket are changed to
PICKEDUP
and it will proceed in the same way as steps 6 and 7. - After a delivery man completes delivery, API
/delivery/complete
will be called and sendDELIVERY_COMPLETE
event. - State fields of order and ticket are changed to
COMPLETE
and it will proceed in the same way as steps 6 and 7.
Use SERVICE-DB-Message Relay-Kafka transfer flow, not SERVICE to Kafka direct transfer.
Therefore, the act of sending a message to the topic in the above description is that the message stored in the OUTBOX
table by the service was read by the message relay and sent to the Kafka.
-
Build applications and docker images
make compile
-
Launch applications and Wait until the servers to run (jq required)
make run
-
Testing event flows (jq required)
make test
-
Cleanup
make clean
Library issues that occurred during development have been summarized in ISSUE.md.