Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/ddd-by-examples/all-things-cqrs
Comprehensive guide to a couple of possible ways of synchronizing two states with Spring tools. Synchronization is shown by separating command and queries in a simple CQRS application.
https://github.com/ddd-by-examples/all-things-cqrs
cqrs ddd debezium domain-driven-design events kafka kafka-connect mongodb spring spring-boot
Last synced: 19 days ago
JSON representation
Comprehensive guide to a couple of possible ways of synchronizing two states with Spring tools. Synchronization is shown by separating command and queries in a simple CQRS application.
- Host: GitHub
- URL: https://github.com/ddd-by-examples/all-things-cqrs
- Owner: ddd-by-examples
- Created: 2018-08-17T08:11:04.000Z (over 6 years ago)
- Default Branch: master
- Last Pushed: 2019-04-04T09:49:43.000Z (over 5 years ago)
- Last Synced: 2024-11-01T20:36:21.066Z (about 1 month ago)
- Topics: cqrs, ddd, debezium, domain-driven-design, events, kafka, kafka-connect, mongodb, spring, spring-boot
- Language: Java
- Homepage:
- Size: 3.55 MB
- Stars: 601
- Watchers: 57
- Forks: 149
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
- dev-stuff - All Things CQRS
- awesome-ccamel - ddd-by-examples/all-things-cqrs - Comprehensive guide to a couple of possible ways of synchronizing two states with Spring tools. Synchronization is shown by separating command and queries in a simple CQRS application. (Java)
README
# All Things CQRS
A bunch of ways of doing [CQRS](https://martinfowler.com/bliki/CQRS.html) with various [Spring](https://spring.io) tools.
## Getting Started
These instructions will get you and overview of how to synchronize two different datasources. We will do so by separating command and queries in a simple CQRS app. Each module represents a different way of introducing this pattern. Also, each module is a standalone [Spring Boot](https://spring.io/projects/spring-boot) application.
### Prerequisites
What things you need to run the software:
* Java 8+
* [docker-compose](https://docs.docker.com/compose/)## Overview
Sample applications are based on a simple domain that serves credit cards. There are two usecases:
* Money can be withdrawn from a card (*Withdraw* **command**)
* List of withdrawals from a card can be read (**query**)The important is that:
```
After a successful Withdraw command, a withdrawal should be seen in a result from list of withdrawals query.
```Hence there is a need for some **synchronization** that makes state for commands and queries consistent.
Let's agree on a color code for commands, queries and synchronization. It will make our drawings consistent.
![color code](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/colorcode.jpg "Color code")
### Commands and queries handled in one class (no CQRS)
Code can be found under [in-one-class](https://github.com/ddd-by-examples/all-things-cqrs/tree/master/in-one-class) module.
Running the app:
```
mvn spring-boot:run
```A sample *Withdraw* command:
```
curl localhost:8080/withdrawals -X POST --header 'Content-Type: application/json' -d '{"card":"3a3e99f0-5ad9-47fa-961d-d75fab32ef0e", "amount": 10.00}' --verbose
```
Verifed by a query:
```
curl http://localhost:8080/withdrawals?cardId=3a3e99f0-5ad9-47fa-961d-d75fab32ef0e --verbose
```
Expected result:
```
[{"amount":10.00}]
```Architecture overview:
![in-one-class](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/inoneclass.jpg)
Automatic E2E test for REST API can be found [here](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/in-one-class/src/test/java/io/dddbyexamples/cqrs/CommandQuerySynchronizationTest.java):
```java
@Test
public void shouldSynchronizeQuerySideAfterSendingACommand() {
// given
UUID cardUUid = thereIsCreditCardWithLimit(new BigDecimal(100)); //HTTP POST
// when
clientWantsToWithdraw(TEN, cardUUid); //HTTP GET
// then
thereIsOneWithdrawalOf(TEN, cardUUid);
}
```### CQRS with application service as explicit synchronization
Code can be found under [explicit-with-dto](https://github.com/ddd-by-examples/all-things-cqrs/tree/master/explicit-with-dto) module. Same version, but with JPA entities as results of a query can be found [here](https://github.com/ddd-by-examples/all-things-cqrs/tree/master/explicit-with-entity).
Running the app:
```
mvn spring-boot:run
```A sample *Withdraw* command:
```
curl localhost:8080/withdrawals -X POST --header 'Content-Type: application/json' -d '{"card":"3a3e99f0-5ad9-47fa-961d-d75fab32ef0e", "amount": 10.00}' --verbose
```
Verifed by a query:
```
curl http://localhost:8080/withdrawals?cardId=3a3e99f0-5ad9-47fa-961d-d75fab32ef0e --verbose
```
Expected result:
```
[{"amount":10.00}]
```Architecture overview:
![application-process](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/appprocess.jpg)
Automatic E2E test for REST API can be found [here](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/explicit-with-dto/src/test/java/io/dddbyexamples/cqrs/CommandQuerySynchronizationTest.java):
```java
@Test
public void shouldSynchronizeQuerySideAfterSendingACommand() {
// given
UUID cardUUid = thereIsCreditCardWithLimit(new BigDecimal(100)); //HTTP POST
// when
clientWantsToWithdraw(TEN, cardUUid); //HTTP GET
// then
thereIsOneWithdrawalOf(TEN, cardUUid);
}
```### CQRS with spring application events as implicit synchronization
Code can be found under [with-application-events](https://github.com/ddd-by-examples/all-things-cqrs/tree/master/with-application-events) module.
There is also a version with immutable domain module which just returns events. It Can be found [here](https://github.com/ddd-by-examples/all-things-cqrs/tree/master/with-application-events-immutable).
Running the app:
```
mvn spring-boot:run
```A sample *Withdraw* command:
```
curl localhost:8080/withdrawals -X POST --header 'Content-Type: application/json' -d '{"card":"3a3e99f0-5ad9-47fa-961d-d75fab32ef0e", "amount": 10.00}' --verbose
```
Verifed by a query:
```
curl http://localhost:8080/withdrawals?cardId=3a3e99f0-5ad9-47fa-961d-d75fab32ef0e --verbose
```
Expected result:
```
[{"amount":10.00}]
```Architecture overview:
![appevents](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/appevents.jpeg)
Automatic E2E test for REST API can be found [here](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/with-application-events/src/test/java/io/dddbyexamples/cqrs/CommandQuerySynchronizationTest.java):
```java
@Test
public void shouldSynchronizeQuerySideAfterSendingACommand() {
// given
UUID cardUUid = thereIsCreditCardWithLimit(new BigDecimal(100)); //HTTP POST
// when
clientWantsToWithdraw(TEN, cardUUid); //HTTP GET
// then
thereIsOneWithdrawalOf(TEN, cardUUid);
}
```### CQRS with trigger as implicit synchronization
Code can be found under [trigger](https://github.com/ddd-by-examples/all-things-cqrs/tree/master/with-trigger) module.
Running the app:
```
mvn spring-boot:run
```A sample *Withdraw* command:
```
curl localhost:8080/withdrawals -X POST --header 'Content-Type: application/json' -d '{"card":"3a3e99f0-5ad9-47fa-961d-d75fab32ef0e", "amount": 10.00}' --verbose
```
Verifed by a query:
```
curl http://localhost:8080/withdrawals?cardId=3a3e99f0-5ad9-47fa-961d-d75fab32ef0e --verbose
```
Expected result:
```
[{"amount":10.00}]
```Architecture overview:
![trigger](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/trigger.jpg)
Automatic E2E test for REST API can be found [here](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/with-trigger/src/test/java/io/dddbyexamples/cqrs/CommandQuerySynchronizationTest.java):
```java
@Test
public void shouldSynchronizeQuerySideAfterSendingACommand() {
// given
UUID cardUUid = thereIsCreditCardWithLimit(new BigDecimal(100)); //HTTP POST
// when
clientWantsToWithdraw(TEN, cardUUid); //HTTP GET
// then
thereIsOneWithdrawalOf(TEN, cardUUid);
}
```### CQRS with transaction log tailing as synchronization
Synchronization done by listening to database's [transaction log](https://en.wikipedia.org/wiki/Transaction_log), which is a log of transactions accepted by a database management system.
Code can be found under [with-log-tailing](https://github.com/ddd-by-examples/all-things-cqrs/tree/master/with-log-tailing) module.
Additional components:
* MySQL to keep withdrawals and credit cards.
* [Apache Kafka](https://kafka.apache.org) for pub/sub for messages read from database transaction log (in this case it is MySQL).
* [Kafka Connect](https://www.confluent.io/product/connectors/) with [Debezium](https://debezium.io) to read MySQL’s transaction log and stream messages to Kafka’s topic.
* [Spring Cloud Stream](https://cloud.spring.io/spring-cloud-stream/) to read messages from Kafka’s topic.Running the app, remember to be in **root** of the project:
* In *docker-compose.yaml*, under service *kafka* - **CHANGE** IP to match your host machine. Keep port pointing to 9092:
```
ADVERTISED_LISTENERS=PLAINTEXT://YOUR_HOST_IP:9092
```
* Run the whole infrastructure:
```
docker-compose up
```
* Tell Kafka Connect to tail transaction log of MySQL DB and send messages to Kafka:
```
curl -i -X POST -H "Accept:application/json" -H "Content-Type:application/json" http://localhost:8083/connectors/ -d @source.json --verbose
```
A sample *Withdraw* command:
```
curl localhost:8080/withdrawals -X POST --header 'Content-Type: application/json' -d '{"card":"3a3e99f0-5ad9-47fa-961d-d75fab32ef0e", "amount": 10.00}' --verbose
```
Verifed by a query:
```
curl http://localhost:8080/withdrawals?cardId=3a3e99f0-5ad9-47fa-961d-d75fab32ef0e --verbose
```
Expected result can be seen below. Remember that it takes time to read transaction log and create a withdrawal. Hence a withdrawal might be not immedietly seen:
```
[{"amount":10.00}]
```Architecture overview:
![logtailing](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/transactionlog.jpg)
Since it is problematic (or immposible) to test transaction log tailing, there is no E2E test that verifies commands and queries. But we can test if a message arrival in Kafka's topic results in a proper withdrawal created. The code is [here](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/with-log-tailing/src/test/java/io/dddbyexamples/cqrs/sink/ReadModelUpdaterTest.java):
```java
@Test
public void shouldSynchronizeQuerySideAfterLogTailing() {
// given
String cardUUid = thereIsCreditCardWithLimit(new BigDecimal(100));
// when
creditCardUpdateReadFromDbTransactionLog(TEN, cardUUid);
// then
thereIsOneWithdrawalOf(TEN, cardUUid);
}
```
### CQRS with Domain Events as synchronizationSynchronization done by sending a domain event after succesfully handling a command.
Code can be found under [events](https://github.com/ddd-by-examples/all-things-cqrs/tree/master/with-events) module. It has 2 further modules, architecture is fully distributed. There is a [source](https://github.com/ddd-by-examples/all-things-cqrs/tree/master/with-events/with-events-source) (deals with commands) and [sink](https://github.com/ddd-by-examples/all-things-cqrs/tree/master/with-events/with-events-sink) (deals with queries).
Additional components:
* H2 DB to keep credit cards.
* [MongoDB](https://www.mongodb.com/what-is-mongodb) to keep withdrawals.
* Spring Data Reactive MongoDb to reactively talk to Mongo
* [Project Reactor](http://projectreactor.io) to serve non-blocking web-service
* [Apache Kafka](https://kafka.apache.org) for pub/sub for domain events
* [Spring Cloud Stream](https://cloud.spring.io/spring-cloud-stream/) to read/write messages from/to Kafka’s topic.Running the app, remember to be in **root** of the project:
* Run the whole infrastructure:
```
docker-compose up
```A sample *Withdraw* command:
```
curl localhost:8080/withdrawals -X POST --header 'Content-Type: application/json' -d '{"card":"3a3e99f0-5ad9-47fa-961d-d75fab32ef0e", "amount": 10.00}' --verbose
```
Verifed by a query (notifce a different port: **8888**!):
```
curl http://localhost:8888/withdrawals?cardId=3a3e99f0-5ad9-47fa-961d-d75fab32ef0e --verbose
```
Expected result can be seen below. Remember that it takes time to publish and read domain events from Kafka. Hence a withdrawal might be not immedietly seen:
```
[{"amount":10.00}]
```Architecture overview:
![events](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/events.jpg)
Since it is not recommended to test 2 microservices in one test, there is no E2E test that verifies commands and queries. But we can test if a message arrival in Kafka's topic results in a proper withdrawal created. The code is [here](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/with-events/with-events-sink/src/test/java/io/dddbyexamples/cqrs/sink/ReadModelUpdaterTest.java):
```java
@Test
public void shouldSeeWithdrawalAfterGettingAnEvent() {
//when
anEventAboutWithdrawalCame(TEN, cardID);//then
thereIsOneWithdrawalOf(TEN, cardID);
}
```
Also it is possible to test if a successful withdrawal is followed eventually by a proper domain event publication. The code is [here](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/with-events/with-events-source/src/test/java/io/dddbyexamples/cqrs/EventsPublishingTest.java).```java
@Test
public void shouldEventuallySendAnEventAboutCardWithdrawal() throws IOException {
// given
UUID cardUUid = thereIsCreditCardWithLimit(new BigDecimal(100));
// when
clientWantsToWithdraw(TEN, cardUUid);
// then
await().atMost(FIVE_SECONDS).until(() -> eventAboutWithdrawalWasSent(TEN, cardUUid));
}
```### CQRS with Axon Framework
Take a look [here](https://github.com/pivotalsoftware/ESarch)