{"id":13516307,"url":"https://github.com/ddd-by-examples/all-things-cqrs","last_synced_at":"2025-04-05T00:08:00.162Z","repository":{"id":53050770,"uuid":"145091864","full_name":"ddd-by-examples/all-things-cqrs","owner":"ddd-by-examples","description":"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.","archived":false,"fork":false,"pushed_at":"2019-04-04T09:49:43.000Z","size":3725,"stargazers_count":603,"open_issues_count":1,"forks_count":148,"subscribers_count":55,"default_branch":"master","last_synced_at":"2025-03-28T23:06:25.875Z","etag":null,"topics":["cqrs","ddd","debezium","domain-driven-design","events","kafka","kafka-connect","mongodb","spring","spring-boot"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ddd-by-examples.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-08-17T08:11:04.000Z","updated_at":"2025-03-05T03:46:57.000Z","dependencies_parsed_at":"2022-08-24T12:10:13.130Z","dependency_job_id":null,"html_url":"https://github.com/ddd-by-examples/all-things-cqrs","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ddd-by-examples%2Fall-things-cqrs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ddd-by-examples%2Fall-things-cqrs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ddd-by-examples%2Fall-things-cqrs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ddd-by-examples%2Fall-things-cqrs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ddd-by-examples","download_url":"https://codeload.github.com/ddd-by-examples/all-things-cqrs/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247266564,"owners_count":20910836,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cqrs","ddd","debezium","domain-driven-design","events","kafka","kafka-connect","mongodb","spring","spring-boot"],"created_at":"2024-08-01T05:01:21.221Z","updated_at":"2025-04-05T00:08:00.144Z","avatar_url":"https://github.com/ddd-by-examples.png","language":"Java","funding_links":[],"categories":["Uncategorized","Java"],"sub_categories":["Uncategorized"],"readme":"# All Things CQRS\n\nA bunch of ways of doing [CQRS](https://martinfowler.com/bliki/CQRS.html) with various [Spring](https://spring.io) tools.\n\n## Getting Started\n\nThese 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. \n\n### Prerequisites\n\nWhat things you need to run the software:\n\n* Java 8+\n* [docker-compose](https://docs.docker.com/compose/)\n\n## Overview\n\nSample applications are based on a simple domain that serves credit cards. There are two usecases:\n\n*  Money can be withdrawn from a card (*Withdraw* **command**)\n*  List of withdrawals from a card can be read (**query**)\n\nThe important is that:\n```\nAfter a successful Withdraw command, a withdrawal should be seen in a result from list of withdrawals query.\n```\n\nHence there is a need for some **synchronization** that makes state for commands and queries consistent.\n\nLet's agree on a color code for commands, queries and synchronization. It will make our drawings consistent.\n\n![color code](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/colorcode.jpg \"Color code\")\n\n### Commands and queries handled in one class (no CQRS)\n\nCode can be found under [in-one-class](https://github.com/ddd-by-examples/all-things-cqrs/tree/master/in-one-class) module. \n\nRunning the app:\n```\nmvn spring-boot:run\n```\n\nA sample *Withdraw* command:\n\n```\ncurl localhost:8080/withdrawals -X POST --header 'Content-Type: application/json' -d '{\"card\":\"3a3e99f0-5ad9-47fa-961d-d75fab32ef0e\", \"amount\": 10.00}' --verbose\n```\nVerifed by a query:\n```\ncurl http://localhost:8080/withdrawals?cardId=3a3e99f0-5ad9-47fa-961d-d75fab32ef0e --verbose\n```\nExpected result:\n```\n[{\"amount\":10.00}]\n```\n\nArchitecture overview:\n\n![in-one-class](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/inoneclass.jpg)\n\nAutomatic 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):\n\n```java\n    @Test\n    public void shouldSynchronizeQuerySideAfterSendingACommand() {\n        // given\n        UUID cardUUid = thereIsCreditCardWithLimit(new BigDecimal(100)); //HTTP POST\n        // when\n        clientWantsToWithdraw(TEN, cardUUid); //HTTP GET\n        // then\n        thereIsOneWithdrawalOf(TEN, cardUUid);\n    }\n```\n\n### CQRS with application service as explicit synchronization\n\nCode 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).\n\nRunning the app:\n```\nmvn spring-boot:run\n```\n\nA sample *Withdraw* command:\n\n```\ncurl localhost:8080/withdrawals -X POST --header 'Content-Type: application/json' -d '{\"card\":\"3a3e99f0-5ad9-47fa-961d-d75fab32ef0e\", \"amount\": 10.00}' --verbose\n```\nVerifed by a query:\n```\ncurl http://localhost:8080/withdrawals?cardId=3a3e99f0-5ad9-47fa-961d-d75fab32ef0e --verbose\n```\nExpected result:\n```\n[{\"amount\":10.00}]\n```\n\nArchitecture overview:\n\n![application-process](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/appprocess.jpg) \n\nAutomatic 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):\n\n```java\n    @Test\n    public void shouldSynchronizeQuerySideAfterSendingACommand() {\n        // given\n        UUID cardUUid = thereIsCreditCardWithLimit(new BigDecimal(100)); //HTTP POST\n        // when\n        clientWantsToWithdraw(TEN, cardUUid); //HTTP GET\n        // then\n        thereIsOneWithdrawalOf(TEN, cardUUid);\n    }\n```\n\n### CQRS with spring application events as implicit synchronization\n\nCode can be found under [with-application-events](https://github.com/ddd-by-examples/all-things-cqrs/tree/master/with-application-events) module.\n\nThere 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).\n\nRunning the app:\n```\nmvn spring-boot:run\n```\n\nA sample *Withdraw* command:\n\n```\ncurl localhost:8080/withdrawals -X POST --header 'Content-Type: application/json' -d '{\"card\":\"3a3e99f0-5ad9-47fa-961d-d75fab32ef0e\", \"amount\": 10.00}' --verbose\n```\nVerifed by a query:\n```\ncurl http://localhost:8080/withdrawals?cardId=3a3e99f0-5ad9-47fa-961d-d75fab32ef0e --verbose\n```\nExpected result:\n```\n[{\"amount\":10.00}]\n```\n\nArchitecture overview:\n\n![appevents](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/appevents.jpeg) \n\nAutomatic 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):\n\n```java\n    @Test\n    public void shouldSynchronizeQuerySideAfterSendingACommand() {\n        // given\n        UUID cardUUid = thereIsCreditCardWithLimit(new BigDecimal(100)); //HTTP POST\n        // when\n        clientWantsToWithdraw(TEN, cardUUid); //HTTP GET\n        // then\n        thereIsOneWithdrawalOf(TEN, cardUUid);\n    }\n```\n\n### CQRS with trigger as implicit synchronization\n\nCode can be found under [trigger](https://github.com/ddd-by-examples/all-things-cqrs/tree/master/with-trigger) module.\n\nRunning the app:\n```\nmvn spring-boot:run\n```\n\nA sample *Withdraw* command:\n\n```\ncurl localhost:8080/withdrawals -X POST --header 'Content-Type: application/json' -d '{\"card\":\"3a3e99f0-5ad9-47fa-961d-d75fab32ef0e\", \"amount\": 10.00}' --verbose\n```\nVerifed by a query:\n```\ncurl http://localhost:8080/withdrawals?cardId=3a3e99f0-5ad9-47fa-961d-d75fab32ef0e --verbose\n```\nExpected result:\n```\n[{\"amount\":10.00}]\n```\n\nArchitecture overview:\n\n![trigger](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/trigger.jpg) \n\nAutomatic 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):\n\n```java\n    @Test\n    public void shouldSynchronizeQuerySideAfterSendingACommand() {\n        // given\n        UUID cardUUid = thereIsCreditCardWithLimit(new BigDecimal(100)); //HTTP POST\n        // when\n        clientWantsToWithdraw(TEN, cardUUid); //HTTP GET\n        // then\n        thereIsOneWithdrawalOf(TEN, cardUUid);\n    }\n```\n\n### CQRS with transaction log tailing as synchronization\n\nSynchronization 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.\n\nCode can be found under [with-log-tailing](https://github.com/ddd-by-examples/all-things-cqrs/tree/master/with-log-tailing) module.\n\nAdditional components:\n*  MySQL to keep withdrawals and credit cards.\n*  [Apache Kafka](https://kafka.apache.org) for pub/sub for messages read from database transaction log (in this case it is MySQL).\n*  [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.\n*  [Spring Cloud Stream](https://cloud.spring.io/spring-cloud-stream/) to read messages from Kafka’s topic.\n\n\nRunning the app, remember to be in **root** of the project:\n\n* In *docker-compose.yaml*, under service *kafka* - **CHANGE** IP to match your host machine. Keep port pointing to 9092:\n```\nADVERTISED_LISTENERS=PLAINTEXT://YOUR_HOST_IP:9092\n```\n* Run the whole infrastructure:\n```\ndocker-compose up\n```\n* Tell Kafka Connect to tail transaction log of MySQL DB and send messages to Kafka:\n```\ncurl -i -X POST -H \"Accept:application/json\" -H  \"Content-Type:application/json\" http://localhost:8083/connectors/ -d @source.json --verbose\n```\nA sample *Withdraw* command:\n```\ncurl localhost:8080/withdrawals -X POST --header 'Content-Type: application/json' -d '{\"card\":\"3a3e99f0-5ad9-47fa-961d-d75fab32ef0e\", \"amount\": 10.00}' --verbose\n```\nVerifed by a query:\n```\ncurl http://localhost:8080/withdrawals?cardId=3a3e99f0-5ad9-47fa-961d-d75fab32ef0e --verbose\n```\nExpected 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:\n```\n[{\"amount\":10.00}]\n```\n\nArchitecture overview:\n\n![logtailing](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/transactionlog.jpg) \n\nSince 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):\n\n```java\n    @Test\n    public void shouldSynchronizeQuerySideAfterLogTailing() {\n        // given\n        String cardUUid = thereIsCreditCardWithLimit(new BigDecimal(100));\n        // when\n        creditCardUpdateReadFromDbTransactionLog(TEN, cardUUid);\n        // then\n        thereIsOneWithdrawalOf(TEN, cardUUid);\n    }\n```\n### CQRS with Domain Events as synchronization\n\nSynchronization done by sending a domain event after succesfully handling a command.\n\nCode 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).\n\n\nAdditional components:\n*  H2 DB to keep credit cards.\n*  [MongoDB](https://www.mongodb.com/what-is-mongodb) to keep withdrawals.\n*  Spring Data Reactive MongoDb to reactively talk to Mongo\n*  [Project Reactor](http://projectreactor.io) to serve non-blocking web-service\n*  [Apache Kafka](https://kafka.apache.org) for pub/sub for domain events\n*  [Spring Cloud Stream](https://cloud.spring.io/spring-cloud-stream/) to read/write messages from/to Kafka’s topic.\n\n\nRunning the app, remember to be in **root** of the project:\n\n*  Run the whole infrastructure:\n```\ndocker-compose up\n```\n\nA sample *Withdraw* command:\n```\ncurl localhost:8080/withdrawals -X POST --header 'Content-Type: application/json' -d '{\"card\":\"3a3e99f0-5ad9-47fa-961d-d75fab32ef0e\", \"amount\": 10.00}' --verbose\n```\nVerifed by a query (notifce a different port: **8888**!):\n```\ncurl http://localhost:8888/withdrawals?cardId=3a3e99f0-5ad9-47fa-961d-d75fab32ef0e --verbose\n```\nExpected 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:\n```\n[{\"amount\":10.00}]\n```\n\nArchitecture overview:\n\n![events](https://github.com/ddd-by-examples/all-things-cqrs/blob/master/events.jpg) \n\nSince 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):\n\n```java\n    @Test\n    public void shouldSeeWithdrawalAfterGettingAnEvent() {\n        //when\n        anEventAboutWithdrawalCame(TEN, cardID);\n\n        //then\n        thereIsOneWithdrawalOf(TEN, cardID);\n    }\n```\nAlso 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). \n\n```java\n    @Test\n    public void shouldEventuallySendAnEventAboutCardWithdrawal() throws IOException {\n        // given\n        UUID cardUUid = thereIsCreditCardWithLimit(new BigDecimal(100));\n        // when\n        clientWantsToWithdraw(TEN, cardUUid);\n        // then\n        await().atMost(FIVE_SECONDS).until(() -\u003e eventAboutWithdrawalWasSent(TEN, cardUUid));\n    }\n```\n\n### CQRS with Axon Framework\nTake a look [here](https://github.com/pivotalsoftware/ESarch)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fddd-by-examples%2Fall-things-cqrs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fddd-by-examples%2Fall-things-cqrs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fddd-by-examples%2Fall-things-cqrs/lists"}