├── .circleci ├── config.yml ├── generate-config.sh ├── setenv-circle-ci.sh └── upgrade-docker-compose.sh ├── .gitignore ├── README.md ├── _build-and-test-all.sh ├── build-and-test-all-eventuate-local-mysql.sh ├── build-and-test-all-eventuate-local-postgres-polling.sh ├── build-and-test-all-eventuate-local-postgres-wal.sh ├── build-and-test-everything.sh ├── build.gradle ├── buildSrc └── src │ └── main │ └── groovy │ └── VerifyMongoDBConfigurationPlugin.groovy ├── common-contracts ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties ├── META-INF │ └── MANIFEST.MF ├── mvnw ├── pom.xml └── src │ └── main │ └── resources │ └── contracts │ ├── shouldRejectNonExistentCustomer.groovy │ └── shouldVerifyCustomerExists.groovy ├── common-swagger └── build.gradle ├── common-test ├── build.gradle └── src │ └── main │ ├── java │ └── net │ │ └── chrisrichardson │ │ └── eventstore │ │ └── examples │ │ └── customersandorders │ │ └── commontest │ │ └── AbstractCustomerAndOrdersIntegrationTest.java │ └── resources │ └── logback.xml ├── common ├── build.gradle └── src │ ├── main │ ├── java │ │ └── net │ │ │ └── chrisrichardson │ │ │ └── eventstore │ │ │ └── examples │ │ │ └── customersandorders │ │ │ └── common │ │ │ └── domain │ │ │ ├── Money.java │ │ │ └── OrderState.java │ └── resources │ │ └── logback.xml │ └── test │ └── java │ └── net │ └── chrisrichardson │ └── eventstore │ └── examples │ └── customersandorders │ └── common │ └── domain │ ├── MoneyPropertyTest.java │ └── MoneyTest.java ├── compile-contracts.sh ├── customers-service-api-messaging ├── build.gradle └── src │ └── main │ └── java │ └── net │ └── chrisrichardson │ └── eventstore │ └── examples │ └── customersandorders │ └── customers │ └── events │ ├── CustomerCreatedEvent.java │ ├── CustomerCreditLimitExceededEvent.java │ ├── CustomerCreditReservedEvent.java │ └── CustomerEvent.java ├── customers-service-api-web ├── build.gradle └── src │ └── main │ └── java │ └── net │ └── chrisrichardson │ └── eventstore │ └── examples │ └── customersandorders │ └── customers │ └── webapi │ ├── CreateCustomerRequest.java │ ├── CreateCustomerResponse.java │ └── GetCustomerResponse.java ├── customers-service ├── Dockerfile ├── build.gradle └── src │ ├── main │ ├── java │ │ └── net │ │ │ └── chrisrichardson │ │ │ └── eventstore │ │ │ └── examples │ │ │ └── customersandorders │ │ │ └── customersservice │ │ │ ├── CustomerConfiguration.java │ │ │ ├── CustomersServiceMain.java │ │ │ ├── domain │ │ │ ├── CreateCustomerCommand.java │ │ │ ├── Customer.java │ │ │ ├── CustomerCommand.java │ │ │ ├── ReserveCreditCommand.java │ │ │ └── ReservedCreditTracker.java │ │ │ ├── service │ │ │ ├── CustomerService.java │ │ │ ├── CustomerServiceImpl.java │ │ │ └── CustomerWorkflow.java │ │ │ └── web │ │ │ ├── CustomerController.java │ │ │ └── CustomerWebConfiguration.java │ └── resources │ │ ├── application-postgres.properties │ │ └── application.properties │ └── test │ └── java │ └── net │ └── chrisrichardson │ └── eventstore │ └── examples │ └── customersandorders │ └── customersservice │ ├── backend │ ├── CustomerMother.java │ ├── CustomerPersistenceTest.java │ ├── CustomerPropertyTest.java │ ├── CustomerServiceInProcessComponentTest.java │ ├── CustomerServiceInProcessComponentTestConfiguration.java │ ├── CustomerServiceTest.java │ └── CustomerTest.java │ └── web │ └── AbstractCustomerServiceContractTest.java ├── docker-compose-env-files ├── db-id-gen.env └── empty.env ├── docker-compose-eventuate-local-mysql-binlog.yml ├── docker-compose-eventuate-local-postgres-polling.yml ├── docker-compose-eventuate-local-postgres-wal.yml ├── e2e-test ├── build.gradle └── src │ └── test │ └── java │ └── net │ └── chrisrichardson │ └── eventstore │ └── examples │ └── customersandorders │ └── e2etests │ ├── CustomersAndOrdersE2ETest.java │ └── CustomersAndOrdersE2ETestConfiguration.java ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── migration-tests ├── build.gradle └── src │ └── test │ ├── java │ └── net │ │ └── chrisrichardson │ │ └── eventstore │ │ └── examples │ │ └── customersandorders │ │ └── migration │ │ └── DbIdMigrationVerificationTest.java │ └── resources │ ├── application-postgres.properties │ └── application.properties ├── mongodb-cli.sh ├── mysql-cli.sh ├── orders-history-view-service-api-web ├── build.gradle └── src │ └── main │ └── java │ └── net │ └── chrisrichardson │ └── eventstore │ └── examples │ └── customersandorders │ └── ordershistory │ └── webapi │ ├── CustomerView.java │ ├── OrderInfo.java │ └── OrderView.java ├── orders-history-view-service ├── Dockerfile ├── build.gradle ├── docker-compose-integration-tests.yml └── src │ ├── main │ ├── java │ │ └── net │ │ │ └── chrisrichardson │ │ │ └── eventstore │ │ │ └── examples │ │ │ └── customersandorders │ │ │ └── ordershistoryviewservice │ │ │ ├── OrderHistoryViewConfiguration.java │ │ │ ├── OrderHistoryViewServiceMain.java │ │ │ ├── domain │ │ │ ├── CustomerViewRepository.java │ │ │ ├── CustomerViewRepositoryCustom.java │ │ │ ├── CustomerViewRepositoryImpl.java │ │ │ ├── OrderHistoryViewMongoConfiguration.java │ │ │ ├── OrderViewRepository.java │ │ │ ├── OrderViewRepositoryCustom.java │ │ │ └── OrderViewRepositoryImpl.java │ │ │ ├── service │ │ │ ├── OrderHistoryViewService.java │ │ │ └── OrderHistoryViewWorkflow.java │ │ │ └── web │ │ │ ├── OrderHistoryViewWebConfiguration.java │ │ │ ├── customers │ │ │ └── CustomerOrderHistoryController.java │ │ │ └── orders │ │ │ └── OrderViewController.java │ └── resources │ │ ├── application.properties │ │ └── logback.xml │ └── test │ └── java │ └── net │ └── chrisrichardson │ └── eventstore │ └── examples │ └── customersandorders │ └── views │ └── orderhistory │ ├── CustomerViewRepositoryIntegrationTest.java │ ├── OrderHistoryViewServiceTest.java │ └── OrderHistoryViewServiceTestConfiguration.java ├── orders-service-api-messaging ├── build.gradle └── src │ └── main │ └── java │ └── net │ └── chrisrichardson │ └── eventstore │ └── examples │ └── customersandorders │ └── orders │ └── events │ ├── OrderApprovedEvent.java │ ├── OrderCreatedEvent.java │ ├── OrderEvent.java │ └── OrderRejectedEvent.java ├── orders-service-api-web ├── build.gradle └── src │ └── main │ └── java │ └── net │ └── chrisrichardson │ └── eventstore │ └── examples │ └── customersandorders │ └── orders │ └── webapi │ ├── CreateOrderRequest.java │ └── CreateOrderResponse.java ├── orders-service ├── Dockerfile ├── build.gradle └── src │ ├── main │ ├── java │ │ └── net │ │ │ └── chrisrichardson │ │ │ └── eventstore │ │ │ └── examples │ │ │ └── customersandorders │ │ │ └── ordersservice │ │ │ ├── OrderConfiguration.java │ │ │ ├── OrdersServiceMain.java │ │ │ ├── domain │ │ │ ├── ApproveOrderCommand.java │ │ │ ├── CreateOrderCommand.java │ │ │ ├── Customer.java │ │ │ └── Order.java │ │ │ ├── service │ │ │ ├── CustomerNotFoundException.java │ │ │ ├── CustomerService.java │ │ │ ├── CustomerServiceProxy.java │ │ │ ├── CustomerServiceProxyConfiguration.java │ │ │ ├── OrderCommand.java │ │ │ ├── OrderService.java │ │ │ ├── OrderServiceImpl.java │ │ │ ├── OrderWorkflow.java │ │ │ └── RejectOrderCommand.java │ │ │ └── web │ │ │ ├── OrderController.java │ │ │ └── OrderWebConfiguration.java │ └── resources │ │ ├── application-postgres.properties │ │ └── application.properties │ └── test │ └── java │ └── net │ └── chrisrichardson │ └── eventstore │ └── examples │ └── customersandorders │ └── ordersservice │ ├── backend │ ├── CustomerServiceProxyIntegrationTest.java │ ├── CustomerServiceProxyIntegrationTestConfiguration.java │ ├── CustomerServiceProxyTest.java │ ├── CustomerServiceProxyWireMockBasedIntegrationTest.java │ ├── OrderServiceInProcessComponentTest.java │ ├── OrderServiceInProcessComponentTestConfiguration.java │ ├── OrderServiceOutOfProcessComponentTest.java │ ├── OrderServiceOutOfProcessComponentTestConfiguration.java │ ├── OrderTest.java │ └── TestData.java │ └── web │ ├── OrderControllerTest.java │ └── OrderMother.java ├── postgres-cli.sh ├── settings.gradle ├── show-urls.sh └── wait-for-services.sh /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | setup: true 3 | orbs: 4 | eventuate-gradle-build-and-test: "eventuate_io/eventuate-gradle-build-and-test@0.2.7" 5 | continuation: circleci/continuation@0.1.2 6 | jobs: 7 | setup: 8 | executor: continuation/default 9 | steps: 10 | - checkout # checkout code 11 | - run: # run a command 12 | name: Generate config 13 | command: | 14 | ./.circleci/generate-config.sh > generated_config.yml 15 | - continuation/continue: 16 | configuration_path: generated_config.yml 17 | 18 | workflows: 19 | setup: 20 | jobs: 21 | - setup 22 | -------------------------------------------------------------------------------- /.circleci/generate-config.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | cat > generated_config.yml <> generated_config.yml < ~/docker-compose 6 | chmod +x ~/docker-compose 7 | sudo mv ~/docker-compose /usr/local/bin/docker-compose 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.ipr 3 | *.iws 4 | .DS_Store 5 | .cache 6 | .classpath 7 | .gradle 8 | .idea 9 | .project 10 | .scala_dependencies 11 | .settings 12 | .springBeans 13 | bin 14 | build 15 | build*.log 16 | classes 17 | node_modules 18 | npm-debug.log 19 | target 20 | out 21 | *.IGNORE 22 | tmp-migration -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is the Java version of the customers and orders example that I've used in numerous presentations 2 | on developing microservices with event sourcing and CQRS. 3 | The code is built using the Eventuate platform. 4 | It illustrates how to implement an eventually consistent credit limit check using event sourcing. 5 | For more information, see this [presentation from Gluecon 2016](http://www.slideshare.net/chris.e.richardson/a-pattern-language-for-microservices-gluecon-2016/24) 6 | 7 | # About Eventuate™ 8 | 9 | ![](http://eventuate.io/i/logo.gif) 10 | 11 | The application is built using [Eventuate](http://eventuate.io/), which is an application platform for writing transactional microservices. 12 | It provides a simple yet powerful event-driven programming model that is based on event sourcing and Command Query Responsibility Segregation (CQRS). 13 | Eventuate solves the distributed data management problems inherent in a microservice architecture. 14 | It consists of a scalable, distributed event store and client libraries for various languages and frameworks including Java, Scala, and the Spring framework. 15 | 16 | # Building and running the application. 17 | 18 | This is a Java 8, Gradle project. However, you do not need to install Gradle since it will be downloaded automatically. You just need to have Java 8 installed. 19 | 20 | 21 | ## Building and running using Eventuate Local 22 | 23 | First, build the application: 24 | 25 | ``` 26 | ./gradlew assemble 27 | ``` 28 | 29 | Next, you can launch the application using [Docker Compose](https://docs.docker.com/compose/) 30 | 31 | Note: 32 | 33 | If the containers aren't accessible via `localhost` - e.g. you are using Docker Toolbox, you will have to use `${DOCKER_HOST_IP}` instead of localhost. 34 | See this http://eventuate.io/docs/usingdocker.html[guide to setting `DOCKER_HOST_IP`] for more information. 35 | 36 | ``` 37 | ./gradlew ComposeBuild 38 | ./gradlew ComposeUp 39 | ``` 40 | 41 | Where `database-mode` is one of: 42 | 43 | * `mysqlbinlog` - use MySQL with Binlog-based event publishing 44 | * `postgreswal` - use Postgres with Postgres WAL-based event publishing 45 | * `postgrespolling` - use Postgres with generic JDBC polling-based event publishing 46 | 47 | Finally, you can use the Swagger UI provided by the services to create customers and orders, and view the order history: 48 | 49 | * `http://localhost:8081/swagger-ui.html` - Create a customer 50 | * `http://localhost:8083/swagger-ui.html` - Create an order 51 | * `http://localhost:8082/swagger-ui.html` - View the customer and the order 52 | 53 | (Hint: best to open these URLs in separate tabs) 54 | 55 | The script `./show-urls.sh` will display the URLs. 56 | -------------------------------------------------------------------------------- /_build-and-test-all.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | set -e 4 | 5 | export COMPOSE_HTTP_TIMEOUT=240 6 | 7 | docker="./gradlew ${DATABASE}${MODE}Compose" 8 | 9 | if [ -z "$SPRING_DATA_MONGODB_URI" ] ; then 10 | export SPRING_DATA_MONGODB_URI=mongodb://${DOCKER_HOST_IP:-localhost}/customers_orders 11 | echo Set SPRING_DATA_MONGODB_URI $SPRING_DATA_MONGODB_URI 12 | fi 13 | 14 | 15 | if [ "$1" = "--use-existing" ] ; then 16 | shift; 17 | else 18 | ${docker}Down 19 | fi 20 | 21 | NO_RM=false 22 | 23 | if [ "$1" = "--no-rm" ] ; then 24 | NO_RM=true 25 | shift 26 | fi 27 | 28 | ./compile-contracts.sh 29 | 30 | ./gradlew --stacktrace $BUILD_AND_TEST_ALL_EXTRA_GRADLE_ARGS $* testClasses 31 | ./gradlew --stacktrace $BUILD_AND_TEST_ALL_EXTRA_GRADLE_ARGS $* build -x :e2e-test:test 32 | 33 | ${docker}Up 34 | 35 | #Testing db cli 36 | if [ "${DATABASE}" == "mysql" ]; then 37 | echo 'show databases;' | ./mysql-cli.sh -i 38 | elif [ "${DATABASE}" == "postgres" ]; then 39 | echo '\l' | ./postgres-cli.sh -i 40 | else 41 | echo "Unknown Database" 42 | exit 99 43 | fi 44 | 45 | #Testing mongo cli 46 | echo 'show dbs' | ./mongodb-cli.sh -i 47 | 48 | set -e 49 | 50 | ./gradlew -a $BUILD_AND_TEST_ALL_EXTRA_GRADLE_ARGS $* :e2e-test:cleanTest :e2e-test:test -P ignoreE2EFailures=false 51 | 52 | ./wait-for-services.sh ${DOCKER_HOST_IP:-localhost} readers/${READER}/finished 8099 53 | 54 | compose="docker-compose -f docker-compose-eventuate-local-${DATABASE}-${MODE}.yml " 55 | 56 | $compose stop cdc-service 57 | curl -s https://raw.githubusercontent.com/eventuate-foundation/eventuate-common/master/migration/db-id/migration.sh &> /dev/stdout | bash 58 | $compose start cdc-service 59 | 60 | ${docker}Up -P envFile=docker-compose-env-files/db-id-gen.env 61 | 62 | ./gradlew -a $BUILD_AND_TEST_ALL_EXTRA_GRADLE_ARGS $* :e2e-test:cleanTest :e2e-test:test -P ignoreE2EFailures=false 63 | 64 | ./gradlew -P verifyDbIdMigration=true :migration-tests:cleanTest migration-tests:test 65 | 66 | if [ $NO_RM = false ] ; then 67 | ${docker}Down 68 | fi 69 | -------------------------------------------------------------------------------- /build-and-test-all-eventuate-local-mysql.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | export EVENTUATE_LOCAL=yes 4 | export READER=MySqlReader 5 | 6 | export DATABASE=mysql 7 | export MODE=binlog 8 | 9 | ./_build-and-test-all.sh 10 | -------------------------------------------------------------------------------- /build-and-test-all-eventuate-local-postgres-polling.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | export EVENTUATE_LOCAL=yes 4 | export READER=PostgresPollingReader 5 | 6 | export DATABASE=postgres 7 | export MODE=polling 8 | 9 | export SPRING_PROFILES_ACTIVE=postgres,EventuatePolling 10 | 11 | ./_build-and-test-all.sh 12 | -------------------------------------------------------------------------------- /build-and-test-all-eventuate-local-postgres-wal.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | export EVENTUATE_LOCAL=yes 4 | export READER=PostgresWalReader 5 | 6 | export DATABASE=postgres 7 | export MODE=wal 8 | 9 | export SPRING_PROFILES_ACTIVE=postgres,PostgresWal 10 | 11 | ./_build-and-test-all.sh 12 | -------------------------------------------------------------------------------- /build-and-test-everything.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | set -e 4 | set -o pipefail 5 | 6 | SCRIPTS="./build-and-test-all-eventuate-local-mysql.sh 7 | ./build-and-test-all-eventuate-local-postgres-polling.sh 8 | ./build-and-test-all-eventuate-local-postgres-wal.sh 9 | " 10 | 11 | date > build-and-test-everything.log 12 | 13 | for script in $SCRIPTS ; do 14 | ./gradlew clean 15 | echo '****************************************** Running' $script 16 | date >> build-and-test-everything.log 17 | echo '****************************************** Running' $script >> build-and-test-everything.log 18 | 19 | (cd $(dirname $script) ; ./$(basename $script) ) | tee -a build-and-test-everything.log 20 | done 21 | 22 | echo 'Finished successfully!!!' 23 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | maven { url = uri("https://plugins.gradle.org/m2/") } 5 | } 6 | dependencies { 7 | classpath("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion") 8 | classpath "com.avast.gradle:gradle-docker-compose-plugin:0.12.0" 9 | } 10 | } 11 | 12 | apply plugin: 'docker-compose' 13 | 14 | allprojects { 15 | group = "net.chrisrichardson.eventstore.javacustomersandorders" 16 | } 17 | 18 | subprojects { 19 | apply plugin: 'java' 20 | sourceCompatibility = 1.8 21 | targetCompatibility = 1.8 22 | 23 | repositories { 24 | mavenCentral() 25 | eventuateMavenRepoUrl.split(',').each { repoUrl -> maven { url repoUrl } } 26 | mavenLocal() 27 | } 28 | 29 | dependencies { 30 | implementation(platform("io.eventuate.platform:eventuate-platform-dependencies:$eventuateBomVersion")) 31 | 32 | testImplementation "junit:junit:4.12" 33 | 34 | } 35 | } 36 | 37 | dockerCompose { 38 | environment.put "EVENTUATE_COMMON_VERSION", eventuateCommonImageVersion 39 | environment.put "EVENTUATE_MESSAGING_KAFKA_IMAGE_VERSION", eventuateMessagingKafkaImageVersion 40 | environment.put "EVENTUATE_CDC_VERSION", eventuateCdcImageVersion 41 | environment.put "EVENTUATE_JAVA_BASE_IMAGE_VERSION", eventuateExamplesBaseImageVersion 42 | 43 | if (project.ext.has("envFile")) { 44 | environment.put "ENV_FILE", project.ext.envFile 45 | } 46 | 47 | mysqlbinlog { 48 | projectName = null 49 | useComposeFiles = ["docker-compose-eventuate-local-mysql-binlog.yml"] 50 | } 51 | 52 | postgrespolling { 53 | projectName = null 54 | useComposeFiles = ["docker-compose-eventuate-local-postgres-polling.yml"] 55 | } 56 | 57 | postgreswal { 58 | projectName = null 59 | useComposeFiles = ["docker-compose-eventuate-local-postgres-wal.yml"] 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /buildSrc/src/main/groovy/VerifyMongoDBConfigurationPlugin.groovy: -------------------------------------------------------------------------------- 1 | import org.gradle.api.* 2 | 3 | 4 | class VerifyMongoDBConfigurationPlugin implements Plugin { 5 | void apply(Project project) { 6 | project.test { 7 | beforeSuite { x -> 8 | if (x.parent == null) { 9 | if (System.getenv("SPRING_DATA_MONGODB_URI") == null) 10 | throw new RuntimeException("Please make sure that the environment variable SPRING_DATA_MONGODB_URI is set, e.g. export SPRING_DATA_MONGODB_URI=mongodb://192.168.59.103/mydb") 11 | } 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /common-contracts/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eventuate-examples/eventuate-examples-java-customers-and-orders/9361f9b8ca88d144f86ec76fab77454e4ff45192/common-contracts/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /common-contracts/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip 2 | -------------------------------------------------------------------------------- /common-contracts/META-INF/MANIFEST.MF: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | 3 | -------------------------------------------------------------------------------- /common-contracts/mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven2 Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # 58 | # Look for the Apple JDKs first to preserve the existing behaviour, and then look 59 | # for the new JDKs provided by Oracle. 60 | # 61 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then 62 | # 63 | # Apple JDKs 64 | # 65 | export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home 66 | fi 67 | 68 | if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then 69 | # 70 | # Apple JDKs 71 | # 72 | export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 73 | fi 74 | 75 | if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then 76 | # 77 | # Oracle JDKs 78 | # 79 | export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home 80 | fi 81 | 82 | if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then 83 | # 84 | # Apple JDKs 85 | # 86 | export JAVA_HOME=`/usr/libexec/java_home` 87 | fi 88 | ;; 89 | esac 90 | 91 | if [ -z "$JAVA_HOME" ] ; then 92 | if [ -r /etc/gentoo-release ] ; then 93 | JAVA_HOME=`java-config --jre-home` 94 | fi 95 | fi 96 | 97 | if [ -z "$M2_HOME" ] ; then 98 | ## resolve links - $0 may be a link to maven's home 99 | PRG="$0" 100 | 101 | # need this for relative symlinks 102 | while [ -h "$PRG" ] ; do 103 | ls=`ls -ld "$PRG"` 104 | link=`expr "$ls" : '.*-> \(.*\)$'` 105 | if expr "$link" : '/.*' > /dev/null; then 106 | PRG="$link" 107 | else 108 | PRG="`dirname "$PRG"`/$link" 109 | fi 110 | done 111 | 112 | saveddir=`pwd` 113 | 114 | M2_HOME=`dirname "$PRG"`/.. 115 | 116 | # make it fully qualified 117 | M2_HOME=`cd "$M2_HOME" && pwd` 118 | 119 | cd "$saveddir" 120 | # echo Using m2 at $M2_HOME 121 | fi 122 | 123 | # For Cygwin, ensure paths are in UNIX format before anything is touched 124 | if $cygwin ; then 125 | [ -n "$M2_HOME" ] && 126 | M2_HOME=`cygpath --unix "$M2_HOME"` 127 | [ -n "$JAVA_HOME" ] && 128 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 129 | [ -n "$CLASSPATH" ] && 130 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 131 | fi 132 | 133 | # For Migwn, ensure paths are in UNIX format before anything is touched 134 | if $mingw ; then 135 | [ -n "$M2_HOME" ] && 136 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 137 | [ -n "$JAVA_HOME" ] && 138 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 139 | # TODO classpath? 140 | fi 141 | 142 | if [ -z "$JAVA_HOME" ]; then 143 | javaExecutable="`which javac`" 144 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 145 | # readlink(1) is not available as standard on Solaris 10. 146 | readLink=`which readlink` 147 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 148 | if $darwin ; then 149 | javaHome="`dirname \"$javaExecutable\"`" 150 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 151 | else 152 | javaExecutable="`readlink -f \"$javaExecutable\"`" 153 | fi 154 | javaHome="`dirname \"$javaExecutable\"`" 155 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 156 | JAVA_HOME="$javaHome" 157 | export JAVA_HOME 158 | fi 159 | fi 160 | fi 161 | 162 | if [ -z "$JAVACMD" ] ; then 163 | if [ -n "$JAVA_HOME" ] ; then 164 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 165 | # IBM's JDK on AIX uses strange locations for the executables 166 | JAVACMD="$JAVA_HOME/jre/sh/java" 167 | else 168 | JAVACMD="$JAVA_HOME/bin/java" 169 | fi 170 | else 171 | JAVACMD="`which java`" 172 | fi 173 | fi 174 | 175 | if [ ! -x "$JAVACMD" ] ; then 176 | echo "Error: JAVA_HOME is not defined correctly." >&2 177 | echo " We cannot execute $JAVACMD" >&2 178 | exit 1 179 | fi 180 | 181 | if [ -z "$JAVA_HOME" ] ; then 182 | echo "Warning: JAVA_HOME environment variable is not set." 183 | fi 184 | 185 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 186 | 187 | # For Cygwin, switch paths to Windows format before running java 188 | if $cygwin; then 189 | [ -n "$M2_HOME" ] && 190 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 191 | [ -n "$JAVA_HOME" ] && 192 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 193 | [ -n "$CLASSPATH" ] && 194 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 195 | fi 196 | 197 | # traverses directory structure from process work directory to filesystem root 198 | # first directory with .mvn subdirectory is considered project base directory 199 | find_maven_basedir() { 200 | local basedir=$(pwd) 201 | local wdir=$(pwd) 202 | while [ "$wdir" != '/' ] ; do 203 | if [ -d "$wdir"/.mvn ] ; then 204 | basedir=$wdir 205 | break 206 | fi 207 | wdir=$(cd "$wdir/.."; pwd) 208 | done 209 | echo "${basedir}" 210 | } 211 | 212 | # concatenates all lines of a file 213 | concat_lines() { 214 | if [ -f "$1" ]; then 215 | echo "$(tr -s '\n' ' ' < "$1")" 216 | fi 217 | } 218 | 219 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} 220 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 221 | 222 | # Provide a "standardized" way to retrieve the CLI args that will 223 | # work with both Windows and non-Windows executions. 224 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 225 | export MAVEN_CMD_LINE_ARGS 226 | 227 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 228 | 229 | exec "$JAVACMD" \ 230 | $MAVEN_OPTS \ 231 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 232 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 233 | ${WRAPPER_LAUNCHER} "$@" 234 | -------------------------------------------------------------------------------- /common-contracts/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 4.0.0 6 | 7 | net.chrisrichardson.eventstore.examples.customersandorders 8 | common-contracts 9 | 1.0-SNAPSHOT 10 | 11 | POM used to install locally stubs for consumer side 12 | 13 | 14 | org.springframework.boot 15 | spring-boot-starter-parent 16 | 2.3.5.RELEASE 17 | 18 | 19 | 20 | 21 | UTF-8 22 | 1.8 23 | 2.2.5.RELEASE 24 | Hoxton.SR9 25 | 26 | true 27 | 28 | true 29 | 30 | 31 | 32 | 33 | 34 | org.springframework.cloud 35 | spring-cloud-dependencies 36 | ${spring-cloud-dependencies.version} 37 | pom 38 | import 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | org.apache.maven.plugins 47 | maven-surefire-plugin 48 | 2.19.1 49 | 50 | true 51 | 52 | 53 | 54 | org.springframework.cloud 55 | spring-cloud-contract-maven-plugin 56 | ${spring-cloud-contract.version} 57 | true 58 | 59 | 60 | ${project.basedir} 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | spring 69 | 70 | true 71 | 72 | 73 | 74 | 75 | spring-snapshots 76 | Spring Snapshots 77 | http://repo.spring.io/libs-snapshot-local 78 | 79 | true 80 | 81 | 82 | 83 | spring-milestones 84 | Spring Milestones 85 | http://repo.spring.io/libs-milestone-local 86 | 87 | false 88 | 89 | 90 | 91 | spring-plugin-snapshots 92 | Spring Snapshots 93 | http://repo.spring.io/plugins-snapshot-local 94 | 95 | true 96 | 97 | 98 | 99 | spring-plugin-milestones 100 | Spring Milestones 101 | http://repo.spring.io/plugins-release-local 102 | 103 | false 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /common-contracts/src/main/resources/contracts/shouldRejectNonExistentCustomer.groovy: -------------------------------------------------------------------------------- 1 | package contracts; 2 | 3 | org.springframework.cloud.contract.spec.Contract.make { 4 | request { 5 | method 'GET' 6 | url '/customers/1223232-none' 7 | } 8 | response { 9 | status 404 10 | } 11 | } -------------------------------------------------------------------------------- /common-contracts/src/main/resources/contracts/shouldVerifyCustomerExists.groovy: -------------------------------------------------------------------------------- 1 | package contracts; 2 | 3 | org.springframework.cloud.contract.spec.Contract.make { 4 | request { 5 | method 'GET' 6 | url '/customers/1223232' 7 | } 8 | response { 9 | status 200 10 | headers { 11 | header('Content-Type': 'application/json') 12 | } 13 | body("{}") 14 | } 15 | } -------------------------------------------------------------------------------- /common-swagger/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | 3 | implementation "org.springdoc:springdoc-openapi-ui:$springDocOpenApiUiVersion" 4 | } 5 | -------------------------------------------------------------------------------- /common-test/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile project(":common") 3 | compile project(":orders-history-view-service-api-web") 4 | 5 | compile "io.reactivex:rxjava:1.1.5" 6 | compile "junit:junit:4.12" 7 | 8 | compile "io.eventuate.util:eventuate-util-test:$eventuateUtilVersion" 9 | 10 | } 11 | -------------------------------------------------------------------------------- /common-test/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/commontest/AbstractCustomerAndOrdersIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.commontest; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.OrderState; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistory.webapi.CustomerView; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistory.webapi.OrderInfo; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistory.webapi.OrderView; 8 | import org.junit.Test; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.util.concurrent.TimeUnit; 13 | 14 | import static io.eventuate.util.test.async.Eventually.eventually; 15 | import static org.junit.Assert.*; 16 | 17 | public abstract class AbstractCustomerAndOrdersIntegrationTest { 18 | protected Logger logger = LoggerFactory.getLogger(getClass()); 19 | 20 | private final Money creditLimit = new Money(1000); 21 | 22 | protected class IntegrationTestCustomerNotFoundException extends RuntimeException { 23 | public IntegrationTestCustomerNotFoundException(Throwable cause) { 24 | super(cause); 25 | } 26 | } 27 | 28 | @Test 29 | public void shouldCreateAndApproveOrder() { 30 | 31 | String customerId = createCustomer(creditLimit); 32 | 33 | Money orderTotal = new Money(720); 34 | 35 | String orderId = createOrder(customerId, orderTotal); 36 | 37 | eventually(80, 500, TimeUnit.MILLISECONDS, () -> { 38 | OrderView o = getOrderView(orderId); 39 | assertNotNull(o); 40 | assertEquals(OrderState.APPROVED, o.getState()); 41 | }); 42 | 43 | eventually(() -> { 44 | CustomerView cv = getCustomerView(customerId); 45 | assertNotNull(cv); 46 | OrderInfo orderInfo = cv.getOrders().get(orderId); 47 | assertNotNull(orderInfo); 48 | assertEquals(OrderState.APPROVED, orderInfo.getState()); 49 | assertEquals(creditLimit, cv.getCreditLimit()); 50 | assertEquals(orderTotal, cv.getOrders().get(orderId).getOrderTotal()); 51 | }); 52 | 53 | 54 | } 55 | 56 | @Test 57 | public void shouldCreateAndRejectOrder() { 58 | 59 | String customerId = createCustomer(creditLimit); 60 | 61 | Money orderTotal = creditLimit.add(new Money(1)); 62 | 63 | String orderId = createOrder(customerId, orderTotal); 64 | 65 | eventually(40, 500, TimeUnit.MILLISECONDS, () -> { 66 | OrderView o = getOrderView(orderId); 67 | assertNotNull(o); 68 | assertEquals(OrderState.REJECTED, o.getState()); 69 | }); 70 | 71 | eventually(() -> { 72 | CustomerView cv = getCustomerView(customerId); 73 | assertNotNull(cv); 74 | OrderInfo orderInfo = cv.getOrders().get(orderId); 75 | assertNotNull(orderInfo); 76 | assertEquals(OrderState.REJECTED, orderInfo.getState()); 77 | assertEquals(creditLimit, cv.getCreditLimit()); 78 | assertEquals(orderTotal, cv.getOrders().get(orderId).getOrderTotal()); 79 | }); 80 | 81 | 82 | } 83 | 84 | @Test 85 | public void shouldRejectOrderWithInvalidCustomerId() { 86 | 87 | Money orderTotal = new Money(720); 88 | 89 | try { 90 | createOrder("unknown-customer-id", orderTotal); 91 | fail(); 92 | } catch (IntegrationTestCustomerNotFoundException e) { 93 | // Expected 94 | } 95 | } 96 | 97 | protected abstract CustomerView getCustomerView(String customerId); 98 | 99 | protected abstract OrderView getOrderView(String orderId); 100 | 101 | protected abstract String createOrder(String customerId, Money orderTotal); 102 | 103 | protected abstract String createCustomer(Money creditLimit); 104 | } 105 | -------------------------------------------------------------------------------- /common-test/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /common/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | dependencies { 3 | compile 'commons-lang:commons-lang:2.6' 4 | compile "io.eventuate.local.java:eventuate-client-java" 5 | 6 | testCompile 'com.pholser:junit-quickcheck-core:0.7' 7 | testCompile 'com.pholser:junit-quickcheck-generators:0.7' 8 | testCompile "junit:junit:4.12" 9 | testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" 10 | testCompile "io.eventuate.local.java:eventuate-client-java-spring-jdbc" 11 | } 12 | -------------------------------------------------------------------------------- /common/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/common/domain/Money.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.common.domain; 2 | 3 | import org.apache.commons.lang.builder.EqualsBuilder; 4 | import org.apache.commons.lang.builder.HashCodeBuilder; 5 | import org.apache.commons.lang.builder.ToStringBuilder; 6 | 7 | import java.math.BigDecimal; 8 | 9 | public class Money { 10 | 11 | public static final Money ZERO = new Money(0); 12 | private BigDecimal amount; 13 | 14 | public Money() { 15 | } 16 | 17 | public Money(int i) { 18 | this.amount = new BigDecimal(i); 19 | } 20 | 21 | public Money(BigDecimal amount) { 22 | this.amount = amount; 23 | } 24 | 25 | /* 26 | If this >= other 27 | */ 28 | public boolean isGreaterThanOrEqual(Money other) { 29 | return amount.compareTo(other.amount) >= 0; 30 | } 31 | public Money add(Money other) { 32 | return new Money(amount.add(other.amount)); 33 | } 34 | public Money subtract(Money other) { 35 | return new Money(amount.subtract(other.amount)); 36 | } 37 | 38 | 39 | @Override 40 | public String toString() { 41 | return ToStringBuilder.reflectionToString(this); 42 | } 43 | 44 | @Override 45 | public boolean equals(Object obj) { 46 | return EqualsBuilder.reflectionEquals(this, obj); 47 | } 48 | 49 | @Override 50 | public int hashCode() { 51 | return HashCodeBuilder.reflectionHashCode(this); 52 | } 53 | 54 | public BigDecimal getAmount() { 55 | return amount; 56 | } 57 | 58 | public void setAmount(BigDecimal amount) { 59 | this.amount = amount; 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /common/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/common/domain/OrderState.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.common.domain; 2 | 3 | public enum OrderState { 4 | APPROVED, REJECTED, state, CREATED 5 | } 6 | -------------------------------------------------------------------------------- /common/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /common/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/common/domain/MoneyPropertyTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.common.domain; 2 | 3 | import com.pholser.junit.quickcheck.Property; 4 | import com.pholser.junit.quickcheck.generator.InRange; 5 | import com.pholser.junit.quickcheck.runner.JUnitQuickcheck; 6 | import org.junit.runner.RunWith; 7 | 8 | import static org.hamcrest.Matchers.greaterThanOrEqualTo; 9 | import static org.hamcrest.Matchers.lessThanOrEqualTo; 10 | import static org.junit.Assert.assertEquals; 11 | import static org.junit.Assume.assumeThat; 12 | 13 | @RunWith(JUnitQuickcheck.class) 14 | public class MoneyPropertyTest { 15 | 16 | @Property 17 | public void add(int x, @InRange(minInt = 0, maxInt = Integer.MAX_VALUE / 2) int y) { 18 | 19 | assumeThat(x, greaterThanOrEqualTo(0)); 20 | assumeThat(x, lessThanOrEqualTo(Integer.MAX_VALUE / 2)); 21 | 22 | assertEquals(new Money(x).add(new Money(y)), new Money(x + y)); 23 | } 24 | 25 | @Property 26 | public void isGreaterThanOrEqual(int x, int y) { 27 | assertEquals(new Money(x).isGreaterThanOrEqual(new Money(y)), x >= y); 28 | } 29 | 30 | @Property 31 | public void equals(int x, int y) { 32 | assertEquals(new Money(x).equals(new Money(y)), x == y); 33 | } 34 | 35 | 36 | } 37 | -------------------------------------------------------------------------------- /common/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/common/domain/MoneyTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.common.domain; 2 | 3 | 4 | import org.junit.Test; 5 | 6 | import java.math.BigDecimal; 7 | 8 | import static org.junit.Assert.assertEquals; 9 | import static org.junit.Assert.assertTrue; 10 | 11 | public class MoneyTest { 12 | 13 | private Money m1 = new Money(10); 14 | private Money m2 = new Money(15); 15 | 16 | @Test 17 | public void shouldReturnAmount() { 18 | assertEquals(new BigDecimal(10), new Money(10).getAmount()); 19 | } 20 | 21 | @Test 22 | public void shouldCompare() { 23 | assertTrue(m2.isGreaterThanOrEqual(m2)); 24 | assertTrue(m2.isGreaterThanOrEqual(m1)); 25 | } 26 | 27 | @Test 28 | public void shouldAdd() { 29 | assertEquals(new Money(25), m1.add(m2)); 30 | } 31 | 32 | @Test 33 | public void shouldSubtract() { 34 | assertEquals(new Money(5), m2.subtract(m1)); 35 | } 36 | 37 | 38 | 39 | } -------------------------------------------------------------------------------- /compile-contracts.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | (cd common-contracts ; ./mvnw install) 4 | -------------------------------------------------------------------------------- /customers-service-api-messaging/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile project(":common") 3 | 4 | testCompile "junit:junit:4.12" 5 | } 6 | -------------------------------------------------------------------------------- /customers-service-api-messaging/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customers/events/CustomerCreatedEvent.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customers.events; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import org.apache.commons.lang.builder.EqualsBuilder; 5 | import org.apache.commons.lang.builder.HashCodeBuilder; 6 | 7 | public class CustomerCreatedEvent implements CustomerEvent { 8 | private String name; 9 | private Money creditLimit; 10 | 11 | @Override 12 | public boolean equals(Object obj) { 13 | return EqualsBuilder.reflectionEquals(this, obj); 14 | } 15 | 16 | @Override 17 | public int hashCode() { 18 | return HashCodeBuilder.reflectionHashCode(this); 19 | } 20 | 21 | private CustomerCreatedEvent() { 22 | } 23 | 24 | public CustomerCreatedEvent(String name, Money creditLimit) { 25 | 26 | this.name = name; 27 | this.creditLimit = creditLimit; 28 | } 29 | 30 | public String getName() { 31 | return name; 32 | } 33 | 34 | public Money getCreditLimit() { 35 | return creditLimit; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /customers-service-api-messaging/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customers/events/CustomerCreditLimitExceededEvent.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customers.events; 2 | 3 | import org.apache.commons.lang.builder.EqualsBuilder; 4 | import org.apache.commons.lang.builder.HashCodeBuilder; 5 | 6 | public class CustomerCreditLimitExceededEvent implements CustomerEvent { 7 | private String orderId; 8 | 9 | @Override 10 | public boolean equals(Object obj) { 11 | return EqualsBuilder.reflectionEquals(this, obj); 12 | } 13 | 14 | @Override 15 | public int hashCode() { 16 | return HashCodeBuilder.reflectionHashCode(this); 17 | } 18 | 19 | public CustomerCreditLimitExceededEvent() { 20 | } 21 | 22 | public CustomerCreditLimitExceededEvent(String orderId) { 23 | this.orderId = orderId; 24 | } 25 | 26 | public String getOrderId() { 27 | return orderId; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /customers-service-api-messaging/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customers/events/CustomerCreditReservedEvent.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customers.events; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import org.apache.commons.lang.builder.EqualsBuilder; 5 | import org.apache.commons.lang.builder.HashCodeBuilder; 6 | 7 | public class CustomerCreditReservedEvent implements CustomerEvent { 8 | private String orderId; 9 | private Money orderTotal; 10 | 11 | @Override 12 | public boolean equals(Object obj) { 13 | return EqualsBuilder.reflectionEquals(this, obj); 14 | } 15 | 16 | @Override 17 | public int hashCode() { 18 | return HashCodeBuilder.reflectionHashCode(this); 19 | } 20 | 21 | public CustomerCreditReservedEvent() { 22 | } 23 | 24 | public CustomerCreditReservedEvent(String orderId, Money orderTotal) { 25 | this.orderId = orderId; 26 | this.orderTotal = orderTotal; 27 | } 28 | 29 | public String getOrderId() { 30 | return orderId; 31 | } 32 | 33 | public Money getOrderTotal() { 34 | return orderTotal; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /customers-service-api-messaging/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customers/events/CustomerEvent.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customers.events; 2 | 3 | 4 | import io.eventuate.Event; 5 | import io.eventuate.EventEntity; 6 | 7 | @EventEntity(entity = "net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.Customer") 8 | public interface CustomerEvent extends Event { 9 | } 10 | -------------------------------------------------------------------------------- /customers-service-api-web/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile project(":common") 3 | 4 | testCompile "junit:junit:4.12" 5 | } 6 | -------------------------------------------------------------------------------- /customers-service-api-web/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customers/webapi/CreateCustomerRequest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customers.webapi; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | 5 | public class CreateCustomerRequest { 6 | private String name; 7 | private Money creditLimit; 8 | 9 | public CreateCustomerRequest() { 10 | } 11 | 12 | public CreateCustomerRequest(String name, Money creditLimit) { 13 | 14 | this.name = name; 15 | this.creditLimit = creditLimit; 16 | } 17 | 18 | 19 | public String getName() { 20 | return name; 21 | } 22 | 23 | public Money getCreditLimit() { 24 | return creditLimit; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /customers-service-api-web/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customers/webapi/CreateCustomerResponse.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customers.webapi; 2 | 3 | 4 | public class CreateCustomerResponse { 5 | private String customerId; 6 | 7 | public CreateCustomerResponse() { 8 | } 9 | 10 | public CreateCustomerResponse(String customerId) { 11 | this.customerId = customerId; 12 | 13 | } 14 | 15 | public String getCustomerId() { 16 | return customerId; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /customers-service-api-web/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customers/webapi/GetCustomerResponse.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customers.webapi; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | 5 | public class GetCustomerResponse { 6 | private String customerId; 7 | private Money creditLimit; 8 | private Money money; 9 | 10 | public GetCustomerResponse() { 11 | } 12 | 13 | public GetCustomerResponse(String customerId, Money creditLimit, Money money) { 14 | this.customerId = customerId; 15 | this.creditLimit = creditLimit; 16 | this.money = money; 17 | } 18 | 19 | public String getCustomerId() { 20 | return customerId; 21 | } 22 | 23 | public void setCustomerId(String customerId) { 24 | this.customerId = customerId; 25 | } 26 | 27 | public Money getCreditLimit() { 28 | return creditLimit; 29 | } 30 | 31 | public void setCreditLimit(Money creditLimit) { 32 | this.creditLimit = creditLimit; 33 | } 34 | 35 | public Money getMoney() { 36 | return money; 37 | } 38 | 39 | public void setMoney(Money money) { 40 | this.money = money; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /customers-service/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG baseImageVersion 2 | FROM eventuateio/eventuate-examples-docker-images-spring-example-base-image:$baseImageVersion 3 | COPY build/libs/customers-service.jar service.jar 4 | -------------------------------------------------------------------------------- /customers-service/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | dependencies { 6 | classpath "io.spring.gradle:dependency-management-plugin:0.6.0.RELEASE" 7 | // if using Stub Runner (consumer side) only remove this dependency 8 | classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:1.1.1.RELEASE" 9 | } 10 | } 11 | 12 | apply plugin: "io.spring.dependency-management" 13 | apply plugin: 'spring-cloud-contract' 14 | 15 | dependencyManagement { 16 | imports { 17 | mavenBom 'org.springframework.cloud:spring-cloud-contract-dependencies:1.1.1.RELEASE' 18 | } 19 | } 20 | 21 | dependencies { 22 | testCompile 'org.springframework.cloud:spring-cloud-starter-contract-verifier' 23 | } 24 | 25 | contracts { 26 | contractsDslDir = new File("../common-contracts/src/main/resources/contracts") 27 | baseClassForTests = 'net.chrisrichardson.eventstore.examples.customersandorders.customersservice.web.AbstractCustomerServiceContractTest' 28 | } 29 | 30 | 31 | apply plugin: 'org.springframework.boot' 32 | 33 | dependencies { 34 | compile project(":customers-service-api-web") 35 | compile project(":customers-service-api-messaging") 36 | compile project(":orders-service-api-messaging") 37 | compile project(":common-swagger") 38 | 39 | testCompile 'com.jayway.restassured:rest-assured:2.9.0' 40 | testCompile 'com.jayway.restassured:spring-mock-mvc:2.9.0' 41 | 42 | compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion" 43 | compile "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion" 44 | compile "io.eventuate.local.java:eventuate-local-java-spring-jdbc-starter" 45 | 46 | testCompile "net.chrisrichardson.eventstore.examples.customersandorders:common-contracts:1.0-SNAPSHOT" 47 | testCompile 'com.pholser:junit-quickcheck-core:0.7' 48 | testCompile 'com.pholser:junit-quickcheck-generators:0.7' 49 | testCompile "junit:junit:4.12" 50 | testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" 51 | testCompile "io.eventuate.local.java:eventuate-client-java-spring-jdbc" 52 | 53 | } 54 | -------------------------------------------------------------------------------- /customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/CustomerConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice; 2 | 3 | import io.eventuate.sync.AggregateRepository; 4 | import io.eventuate.sync.EventuateAggregateStore; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.Customer; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.CustomerCommand; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.service.CustomerService; 8 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.service.CustomerServiceImpl; 9 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.service.CustomerWorkflow; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | @Configuration 14 | public class CustomerConfiguration { 15 | 16 | @Bean 17 | public CustomerWorkflow customerWorkflow() { 18 | return new CustomerWorkflow(); 19 | } 20 | 21 | 22 | @Bean 23 | public CustomerService customerService(AggregateRepository customerRepository) { 24 | return new CustomerServiceImpl(customerRepository); 25 | } 26 | 27 | @Bean 28 | public AggregateRepository customerRepository(EventuateAggregateStore eventStore) { 29 | return new AggregateRepository<>(Customer.class, eventStore); 30 | } 31 | 32 | } 33 | 34 | 35 | -------------------------------------------------------------------------------- /customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/CustomersServiceMain.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.web.CustomerWebConfiguration; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 6 | import org.springframework.context.annotation.ComponentScan; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Import; 9 | 10 | @Configuration 11 | @Import({CustomerConfiguration.class, CustomerWebConfiguration.class,}) 12 | @EnableAutoConfiguration 13 | @ComponentScan 14 | public class CustomersServiceMain { 15 | public static void main(String[] args) { 16 | SpringApplication.run(CustomersServiceMain.class, args); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/domain/CreateCustomerCommand.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.CustomerCommand; 5 | 6 | public class CreateCustomerCommand implements CustomerCommand { 7 | private final String name; 8 | private final Money creditLimit; 9 | 10 | public CreateCustomerCommand(String name, Money creditLimit) { 11 | this.name = name; 12 | this.creditLimit = creditLimit; 13 | } 14 | 15 | public Money getCreditLimit() { 16 | return creditLimit; 17 | } 18 | 19 | public String getName() { 20 | return name; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/domain/Customer.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain; 2 | 3 | import io.eventuate.Event; 4 | import io.eventuate.EventUtil; 5 | import io.eventuate.ReflectiveMutableCommandProcessingAggregate; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.events.CustomerCreatedEvent; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.events.CustomerCreditLimitExceededEvent; 8 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.events.CustomerCreditReservedEvent; 9 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 10 | 11 | import java.util.List; 12 | 13 | public class Customer extends ReflectiveMutableCommandProcessingAggregate { 14 | 15 | private ReservedCreditTracker reservedCreditTracker; 16 | 17 | private Money creditLimit; 18 | private String name; 19 | 20 | public Money availableCredit() { 21 | return creditLimit.subtract(reservedCreditTracker.reservedCredit()); 22 | } 23 | 24 | public Money getCreditLimit() { 25 | return creditLimit; 26 | } 27 | 28 | public List process(CreateCustomerCommand cmd) { 29 | return EventUtil.events(new CustomerCreatedEvent(cmd.getName(), cmd.getCreditLimit())); 30 | } 31 | 32 | public List process(ReserveCreditCommand cmd) { 33 | if (availableCredit().isGreaterThanOrEqual(cmd.getOrderTotal())) 34 | return EventUtil.events(new CustomerCreditReservedEvent(cmd.getOrderId(), cmd.getOrderTotal())); 35 | else 36 | return EventUtil.events(new CustomerCreditLimitExceededEvent(cmd.getOrderId())); 37 | } 38 | 39 | 40 | public void apply(CustomerCreatedEvent event) { 41 | this.name = name; 42 | this.creditLimit = event.getCreditLimit(); 43 | this.reservedCreditTracker = new ReservedCreditTracker(); 44 | } 45 | 46 | public void apply(CustomerCreditReservedEvent event) { 47 | reservedCreditTracker.addReservation(event.getOrderId(), event.getOrderTotal()); 48 | } 49 | 50 | public void apply(CustomerCreditLimitExceededEvent event) { 51 | // Do nothing 52 | } 53 | 54 | 55 | public String getName() { 56 | return name; 57 | } 58 | } 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/domain/CustomerCommand.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain; 2 | 3 | import io.eventuate.Command; 4 | 5 | public interface CustomerCommand extends Command { 6 | } 7 | -------------------------------------------------------------------------------- /customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/domain/ReserveCreditCommand.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | 5 | public class ReserveCreditCommand implements CustomerCommand { 6 | private final Money orderTotal; 7 | private final String orderId; 8 | 9 | public ReserveCreditCommand(Money orderTotal, String orderId) { 10 | this.orderTotal = orderTotal; 11 | this.orderId = orderId; 12 | } 13 | 14 | public Money getOrderTotal() { 15 | return orderTotal; 16 | } 17 | 18 | public String getOrderId() { 19 | return orderId; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/domain/ReservedCreditTracker.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | 8 | public class ReservedCreditTracker { 9 | private Map creditReservations = new HashMap<>(); 10 | 11 | public Money reservedCredit() { 12 | return creditReservations.values().stream().reduce(Money.ZERO, Money::add); 13 | } 14 | 15 | public void addReservation(String orderId, Money orderTotal) { 16 | creditReservations.put(orderId, orderTotal); 17 | } 18 | } -------------------------------------------------------------------------------- /customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/service/CustomerService.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.service; 2 | 3 | import io.eventuate.EntityWithIdAndVersion; 4 | import io.eventuate.EntityWithMetadata; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.Customer; 7 | 8 | public interface CustomerService { 9 | 10 | EntityWithIdAndVersion createCustomer(String name, Money creditLimit); 11 | 12 | EntityWithMetadata findById(String customerId); 13 | } 14 | -------------------------------------------------------------------------------- /customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/service/CustomerServiceImpl.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.service; 2 | 3 | import io.eventuate.sync.AggregateRepository; 4 | import io.eventuate.EntityWithIdAndVersion; 5 | import io.eventuate.EntityWithMetadata; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.CreateCustomerCommand; 8 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.Customer; 9 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.CustomerCommand; 10 | 11 | public class CustomerServiceImpl implements CustomerService { 12 | 13 | private final AggregateRepository customerRepository; 14 | 15 | public CustomerServiceImpl(AggregateRepository customerRepository) { 16 | this.customerRepository = customerRepository; 17 | } 18 | 19 | @Override 20 | public EntityWithIdAndVersion createCustomer(String name, Money creditLimit) { 21 | return customerRepository.save(new CreateCustomerCommand(name, creditLimit)); 22 | } 23 | 24 | @Override 25 | public EntityWithMetadata findById(String customerId) { 26 | return customerRepository.find(customerId); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/service/CustomerWorkflow.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.service; 2 | 3 | import io.eventuate.EntityWithIdAndVersion; 4 | import io.eventuate.EventHandlerContext; 5 | import io.eventuate.EventHandlerMethod; 6 | import io.eventuate.EventSubscriber; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 8 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.Customer; 9 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.ReserveCreditCommand; 10 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.events.OrderCreatedEvent; 11 | 12 | import java.util.concurrent.CompletableFuture; 13 | 14 | @EventSubscriber(id = "customerWorkflow") 15 | public class CustomerWorkflow { 16 | 17 | @EventHandlerMethod 18 | public CompletableFuture> reserveCredit( 19 | EventHandlerContext ctx) { 20 | OrderCreatedEvent event = ctx.getEvent(); 21 | Money orderTotal = event.getOrderTotal(); 22 | String customerId = event.getCustomerId(); 23 | String orderId = ctx.getEntityId(); 24 | 25 | return ctx.update(Customer.class, customerId, new ReserveCreditCommand(orderTotal, orderId)); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/web/CustomerController.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.web; 2 | 3 | import io.eventuate.EntityNotFoundException; 4 | import io.eventuate.EntityWithIdAndVersion; 5 | import io.eventuate.EntityWithMetadata; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.webapi.CreateCustomerRequest; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.webapi.CreateCustomerResponse; 8 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.webapi.GetCustomerResponse; 9 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.Customer; 10 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.service.CustomerService; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.http.ResponseEntity; 14 | import org.springframework.web.bind.annotation.PathVariable; 15 | import org.springframework.web.bind.annotation.RequestBody; 16 | import org.springframework.web.bind.annotation.RequestMapping; 17 | import org.springframework.web.bind.annotation.RequestMethod; 18 | import org.springframework.web.bind.annotation.RestController; 19 | 20 | @RestController 21 | public class CustomerController { 22 | 23 | private CustomerService customerService; 24 | 25 | @Autowired 26 | public CustomerController(CustomerService customerService) { 27 | this.customerService = customerService; 28 | } 29 | 30 | @RequestMapping(value = "/customers", method = RequestMethod.POST) 31 | public CreateCustomerResponse createCustomer(@RequestBody CreateCustomerRequest createCustomerRequest) { 32 | EntityWithIdAndVersion ewidv = customerService.createCustomer(createCustomerRequest.getName(), createCustomerRequest.getCreditLimit()); 33 | return new CreateCustomerResponse(ewidv.getEntityId()); 34 | } 35 | 36 | @RequestMapping(value = "/customers/{customerId}", method = RequestMethod.GET) 37 | public ResponseEntity GetCustomer(@PathVariable String customerId) { 38 | EntityWithMetadata customerWithMetadata; 39 | try { 40 | customerWithMetadata = customerService.findById(customerId); 41 | } catch (EntityNotFoundException e) { 42 | return new ResponseEntity<>(HttpStatus.NOT_FOUND); 43 | } 44 | Customer customer = customerWithMetadata.getEntity(); 45 | GetCustomerResponse response = 46 | new GetCustomerResponse(customerWithMetadata.getEntityIdAndVersion().getEntityId(), customer.getCreditLimit(), 47 | customer.availableCredit()); 48 | return new ResponseEntity<>(response, HttpStatus.OK); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/web/CustomerWebConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.web; 2 | 3 | import org.springframework.boot.autoconfigure.http.HttpMessageConverters; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.ComponentScan; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.http.converter.HttpMessageConverter; 8 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 9 | 10 | @Configuration 11 | @ComponentScan 12 | public class CustomerWebConfiguration { 13 | 14 | @Bean 15 | public HttpMessageConverters customConverters() { 16 | HttpMessageConverter additional = new MappingJackson2HttpMessageConverter(); 17 | return new HttpMessageConverters(additional); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /customers-service/src/main/resources/application-postgres.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:postgresql://${DOCKER_HOST_IP:localhost}/eventuate 2 | spring.datasource.username=eventuate 3 | spring.datasource.password=eventuate 4 | spring.datasource.driver-class-name=org.postgresql.Driver -------------------------------------------------------------------------------- /customers-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | logging.level.io.eventuate.activity=DEBUG 2 | 3 | spring.jpa.generate-ddl=true 4 | 5 | eventuatelocal.kafka.bootstrap.servers=${DOCKER_HOST_IP:localhost}:9092 6 | eventuatelocal.zookeeper.connection.string=${DOCKER_HOST_IP:localhost}:2181 7 | 8 | spring.datasource.url=jdbc:mysql://${DOCKER_HOST_IP:localhost}/eventuate 9 | spring.datasource.username=mysqluser 10 | spring.datasource.password=mysqlpw 11 | spring.datasource.driver-class-name=com.mysql.jdbc.Driver -------------------------------------------------------------------------------- /customers-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/backend/CustomerMother.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.backend; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | 5 | public class CustomerMother { 6 | 7 | static Money creditLimit = new Money(56); 8 | static Money orderTotalThatExceedsCreditLimit = creditLimit.add(new Money(1)); 9 | static String name = "Fred"; 10 | static String orderId = "myorder"; 11 | static Money orderTotalWithinCreditLimit = new Money(5); 12 | } 13 | -------------------------------------------------------------------------------- /customers-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/backend/CustomerPersistenceTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.backend; 2 | 3 | import io.eventuate.EntityWithIdAndVersion; 4 | import io.eventuate.sync.AggregateRepository; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.CreateCustomerCommand; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.Customer; 8 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.CustomerCommand; 9 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.ReserveCreditCommand; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.test.context.junit4.SpringRunner; 15 | 16 | @RunWith(SpringRunner.class) 17 | @SpringBootTest(classes= CustomerServiceInProcessComponentTestConfiguration.class, 18 | webEnvironment= SpringBootTest.WebEnvironment.NONE) 19 | public class CustomerPersistenceTest { 20 | 21 | @Autowired 22 | private AggregateRepository aggregateRepository; 23 | 24 | @Test 25 | public void shouldCreateAndUpdateCustomer() { 26 | EntityWithIdAndVersion cwm = aggregateRepository.save(new CreateCustomerCommand("Fred", new Money(1234))); 27 | 28 | aggregateRepository.update(cwm.getEntityId(), new ReserveCreditCommand(new Money(11), "order-1")); 29 | aggregateRepository.update(cwm.getEntityId(), new ReserveCreditCommand(new Money(11), "order-2")); 30 | aggregateRepository.update(cwm.getEntityId(), new ReserveCreditCommand(new Money(11), "order-3")); 31 | 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /customers-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/backend/CustomerPropertyTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.backend; 2 | 3 | import com.pholser.junit.quickcheck.Property; 4 | import com.pholser.junit.quickcheck.generator.InRange; 5 | import com.pholser.junit.quickcheck.runner.JUnitQuickcheck; 6 | import io.eventuate.Aggregates; 7 | import io.eventuate.DefaultMissingApplyEventMethodStrategy; 8 | import io.eventuate.Event; 9 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.events.CustomerCreatedEvent; 10 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.events.CustomerCreditLimitExceededEvent; 11 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.events.CustomerCreditReservedEvent; 12 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 13 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.Customer; 14 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.ReserveCreditCommand; 15 | import org.junit.runner.RunWith; 16 | 17 | import java.util.List; 18 | 19 | import static java.util.Collections.singletonList; 20 | import static org.junit.Assert.assertEquals; 21 | 22 | @RunWith(JUnitQuickcheck.class) 23 | public class CustomerPropertyTest { 24 | 25 | @Property 26 | public void reserveCredit(@InRange(minInt = 1) int orderTotal) { 27 | Customer customer = Aggregates.recreateAggregate(Customer.class, 28 | singletonList(new CustomerCreatedEvent(CustomerMother.name, CustomerMother.creditLimit)), DefaultMissingApplyEventMethodStrategy.INSTANCE); 29 | 30 | List events = customer.process(new ReserveCreditCommand(new Money(orderTotal), CustomerMother.orderId)); 31 | 32 | assertEquals(1, events.size()); 33 | Class eventClass = events.get(0).getClass(); 34 | 35 | if (CustomerMother.creditLimit.isGreaterThanOrEqual(new Money(orderTotal))) { 36 | assertEquals(CustomerCreditReservedEvent.class, eventClass); 37 | } else 38 | assertEquals(CustomerCreditLimitExceededEvent.class, eventClass); 39 | 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /customers-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/backend/CustomerServiceInProcessComponentTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.backend; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.webapi.CreateCustomerRequest; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.junit4.SpringRunner; 10 | 11 | import static com.jayway.restassured.RestAssured.given; 12 | import static junit.framework.TestCase.assertNotNull; 13 | import static org.junit.Assert.assertEquals; 14 | 15 | @RunWith(SpringRunner.class) 16 | @SpringBootTest(classes= CustomerServiceInProcessComponentTestConfiguration.class, 17 | webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT) 18 | public class CustomerServiceInProcessComponentTest { 19 | 20 | @Value("${local.server.port}") 21 | private int port; 22 | 23 | @Value("${DOCKER_HOST_IP:localhost}") 24 | private String host; 25 | 26 | private String baseUrl(String path) { 27 | return "http://" + host + ":" + port + path; 28 | } 29 | 30 | @Test 31 | public void shouldCreateOrder() { 32 | String postUrl = baseUrl("/customers"); 33 | 34 | String customerId = given(). 35 | body(new CreateCustomerRequest("John Doe", new Money(1234))). 36 | contentType("application/json"). 37 | when(). 38 | post(postUrl). 39 | then(). 40 | statusCode(200). 41 | extract(). 42 | path("customerId"); 43 | 44 | assertNotNull(customerId); 45 | 46 | 47 | Integer creditLimit = given(). 48 | when(). 49 | get(postUrl + "/" + customerId). 50 | then(). 51 | statusCode(200). 52 | extract(). 53 | path("creditLimit.amount"); 54 | 55 | assertEquals(new Integer(1234), creditLimit); 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /customers-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/backend/CustomerServiceInProcessComponentTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.backend; 2 | 3 | import io.eventuate.javaclient.spring.jdbc.EmbeddedTestAggregateStoreConfiguration; 4 | import io.eventuate.local.java.spring.autoconfigure.EventuateDriverAutoConfiguration; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.CustomerConfiguration; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.web.CustomerWebConfiguration; 7 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.annotation.Import; 10 | 11 | @Configuration 12 | @Import({CustomerConfiguration.class, 13 | CustomerWebConfiguration.class, 14 | EmbeddedTestAggregateStoreConfiguration.class 15 | }) 16 | @EnableAutoConfiguration(exclude = {EventuateDriverAutoConfiguration.class}) 17 | public class CustomerServiceInProcessComponentTestConfiguration { 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /customers-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/backend/CustomerServiceTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.backend; 2 | 3 | import io.eventuate.EntityWithIdAndVersion; 4 | import io.eventuate.sync.AggregateRepository; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.CreateCustomerCommand; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.Customer; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.CustomerCommand; 8 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.service.CustomerService; 9 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.service.CustomerServiceImpl; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.mockito.ArgumentCaptor; 13 | 14 | import static org.junit.Assert.assertEquals; 15 | import static org.junit.Assert.assertSame; 16 | import static org.mockito.ArgumentMatchers.any; 17 | import static org.mockito.Mockito.mock; 18 | import static org.mockito.Mockito.verify; 19 | import static org.mockito.Mockito.verifyNoMoreInteractions; 20 | import static org.mockito.Mockito.when; 21 | 22 | public class CustomerServiceTest { 23 | 24 | private CustomerService customerService; 25 | private AggregateRepository aggregateRepository; 26 | 27 | @Before 28 | public void setUp() { 29 | aggregateRepository = mock(AggregateRepository.class); 30 | customerService = new CustomerServiceImpl(aggregateRepository); 31 | } 32 | 33 | @Test 34 | public void shouldCreateCustomer() { 35 | 36 | EntityWithIdAndVersion returned = new EntityWithIdAndVersion<>(null, null); 37 | 38 | when(aggregateRepository.save(any(CreateCustomerCommand.class))).thenReturn(returned); 39 | 40 | EntityWithIdAndVersion result = 41 | customerService.createCustomer(CustomerMother.name, CustomerMother.creditLimit); 42 | 43 | assertSame(returned, result); 44 | 45 | ArgumentCaptor argument = ArgumentCaptor.forClass(CreateCustomerCommand.class); 46 | 47 | verify(aggregateRepository).save(argument.capture()); 48 | verifyNoMoreInteractions(aggregateRepository); 49 | 50 | CreateCustomerCommand command = argument.getValue(); 51 | 52 | assertEquals(CustomerMother.name, command.getName()); 53 | assertEquals(CustomerMother.creditLimit, command.getCreditLimit()); 54 | 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /customers-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/backend/CustomerTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.backend; 2 | 3 | import io.eventuate.Aggregates; 4 | import io.eventuate.DefaultMissingApplyEventMethodStrategy; 5 | import io.eventuate.Event; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.events.CustomerCreatedEvent; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.events.CustomerCreditLimitExceededEvent; 8 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.events.CustomerCreditReservedEvent; 9 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.CreateCustomerCommand; 10 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.Customer; 11 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.CustomerCommand; 12 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.ReserveCreditCommand; 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | 16 | import java.util.List; 17 | 18 | import static io.eventuate.EventUtil.events; 19 | import static org.junit.Assert.assertEquals; 20 | 21 | public class CustomerTest { 22 | 23 | private Customer customer; 24 | private List events; 25 | 26 | @Before 27 | public void createEmptyCustomer() { 28 | customer = new Customer(); 29 | } 30 | 31 | @Test 32 | public void testCreate() { 33 | 34 | process(new CreateCustomerCommand(CustomerMother.name, CustomerMother.creditLimit)); 35 | 36 | assertEventEquals(new CustomerCreatedEvent(CustomerMother.name, CustomerMother.creditLimit)); 37 | 38 | applyEventsToMutableAggregate(); 39 | 40 | assertEquals(CustomerMother.creditLimit, customer.getCreditLimit()); 41 | assertEquals(CustomerMother.creditLimit, customer.availableCredit()); 42 | } 43 | 44 | private void applyEventsToMutableAggregate() { 45 | Aggregates.applyEventsToMutableAggregate(customer, events, DefaultMissingApplyEventMethodStrategy.INSTANCE); 46 | } 47 | 48 | 49 | @Test 50 | public void testReserveCredit() { 51 | initializeCustomer(); 52 | 53 | process(new ReserveCreditCommand(CustomerMother.orderTotalWithinCreditLimit, CustomerMother.orderId)); 54 | 55 | assertEventEquals(new CustomerCreditReservedEvent(CustomerMother.orderId, CustomerMother.orderTotalWithinCreditLimit)); 56 | 57 | applyEventsToMutableAggregate(); 58 | 59 | assertEquals(CustomerMother.creditLimit, customer.getCreditLimit()); 60 | assertEquals(CustomerMother.creditLimit.subtract(CustomerMother.orderTotalWithinCreditLimit), customer.availableCredit()); 61 | 62 | } 63 | 64 | 65 | @Test 66 | public void testCreditLimitExceeded() { 67 | initializeCustomer(); 68 | 69 | process(new ReserveCreditCommand(CustomerMother.orderTotalThatExceedsCreditLimit, CustomerMother.orderId)); 70 | 71 | assertEventEquals(new CustomerCreditLimitExceededEvent(CustomerMother.orderId)); 72 | 73 | applyEventsToMutableAggregate(); 74 | 75 | assertEquals(CustomerMother.creditLimit, customer.getCreditLimit()); 76 | assertEquals(CustomerMother.creditLimit, customer.availableCredit()); 77 | } 78 | 79 | private void process(T command) { 80 | events = customer.processCommand(command); 81 | } 82 | 83 | 84 | private void assertEventEquals(Event expectedEvent) { 85 | assertEquals(events(expectedEvent), events); 86 | } 87 | 88 | private void initializeCustomer() { 89 | process(new CreateCustomerCommand(CustomerMother.name, CustomerMother.creditLimit)); 90 | applyEventsToMutableAggregate(); 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /customers-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/web/AbstractCustomerServiceContractTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.web; 2 | 3 | import com.jayway.restassured.module.mockmvc.RestAssuredMockMvc; 4 | import io.eventuate.Aggregates; 5 | import io.eventuate.DefaultMissingApplyEventMethodStrategy; 6 | import io.eventuate.EntityIdAndVersion; 7 | import io.eventuate.EntityNotFoundException; 8 | import io.eventuate.EntityWithMetadata; 9 | import io.eventuate.common.id.Int128; 10 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.events.CustomerCreatedEvent; 11 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 12 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.domain.Customer; 13 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.service.CustomerService; 14 | import org.junit.Before; 15 | 16 | import java.util.Collections; 17 | import java.util.Optional; 18 | 19 | import static org.mockito.Mockito.mock; 20 | import static org.mockito.Mockito.when; 21 | 22 | public abstract class AbstractCustomerServiceContractTest { 23 | 24 | private CustomerService customerService; 25 | 26 | @Before 27 | public void setup() { 28 | customerService = mock(CustomerService.class); 29 | RestAssuredMockMvc.standaloneSetup(new CustomerController(customerService)); 30 | when(customerService.findById("1223232-none")).thenThrow(new EntityNotFoundException()); 31 | 32 | Customer customer = Aggregates.recreateAggregate(Customer.class, 33 | Collections.singletonList(new CustomerCreatedEvent("Fred", new Money(4566))), DefaultMissingApplyEventMethodStrategy.INSTANCE); 34 | 35 | EntityWithMetadata result = 36 | new EntityWithMetadata<>(new EntityIdAndVersion("1223232", new Int128(1, 2)), Optional.empty(), 37 | Collections.emptyList(), customer); 38 | when(customerService.findById("1223232")) 39 | .thenReturn(result); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /docker-compose-env-files/db-id-gen.env: -------------------------------------------------------------------------------- 1 | EVENTUATE_OUTBOX_ID=1 -------------------------------------------------------------------------------- /docker-compose-env-files/empty.env: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eventuate-examples/eventuate-examples-java-customers-and-orders/9361f9b8ca88d144f86ec76fab77454e4ff45192/docker-compose-env-files/empty.env -------------------------------------------------------------------------------- /docker-compose-eventuate-local-mysql-binlog.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | zookeeper: 4 | image: eventuateio/eventuate-zookeeper:$EVENTUATE_COMMON_VERSION 5 | ports: 6 | - 2181:2181 7 | environment: 8 | ZOOKEEPER_CLIENT_PORT: 2181 9 | KAFKA_HEAP_OPTS: -Xmx64m 10 | 11 | kafka: 12 | image: "eventuateio/eventuate-kafka:$EVENTUATE_MESSAGING_KAFKA_IMAGE_VERSION" 13 | ports: 14 | - 9092:9092 15 | depends_on: 16 | - zookeeper 17 | environment: 18 | KAFKA_LISTENERS: LC://kafka:29092,LX://kafka:9092 19 | KAFKA_ADVERTISED_LISTENERS: LC://kafka:29092,LX://${DOCKER_HOST_IP:-localhost}:9092 20 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LC:PLAINTEXT,LX:PLAINTEXT 21 | KAFKA_INTER_BROKER_LISTENER_NAME: LC 22 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 23 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 24 | KAFKA_HEAP_OPTS: -Xmx192m 25 | 26 | mysql: 27 | image: eventuateio/eventuate-mysql8:$EVENTUATE_COMMON_VERSION 28 | ports: 29 | - 3306:3306 30 | environment: 31 | - MYSQL_ROOT_PASSWORD=rootpassword 32 | - MYSQL_USER=mysqluser 33 | - MYSQL_PASSWORD=mysqlpw 34 | 35 | cdc-service: 36 | image: eventuateio/eventuate-cdc-service:$EVENTUATE_CDC_VERSION 37 | ports: 38 | - "8099:8080" 39 | depends_on: 40 | - mysql 41 | - kafka 42 | - zookeeper 43 | environment: 44 | SPRING_DATASOURCE_URL: jdbc:mysql://mysql/eventuate 45 | SPRING_DATASOURCE_USERNAME: mysqluser 46 | SPRING_DATASOURCE_PASSWORD: mysqlpw 47 | SPRING_DATASOURCE_DRIVER_CLASS_NAME: com.mysql.jdbc.Driver 48 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092 49 | EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181 50 | EVENTUATELOCAL_CDC_DB_USER_NAME: root 51 | EVENTUATELOCAL_CDC_DB_PASSWORD: rootpassword 52 | EVENTUATELOCAL_CDC_READER_NAME: MySqlReader 53 | EVENTUATELOCAL_CDC_OFFSET_STORE_KEY: MySqlBinlog 54 | EVENTUATELOCAL_CDC_MYSQL_BINLOG_CLIENT_UNIQUE_ID: 1234567890 55 | EVENTUATELOCAL_CDC_READ_OLD_DEBEZIUM_DB_OFFSET_STORAGE_TOPIC: "false" 56 | EVENTUATE_OUTBOX_ID: 1 57 | EVENTUATE_CDC_TYPE: "EventuateLocal" 58 | JAVA_OPTS: -Xmx64m 59 | 60 | mongodb: 61 | image: mongo:3.6 62 | hostname: mongodb 63 | command: mongod --smallfiles 64 | ports: 65 | - "27017:27017" 66 | 67 | customers-service: 68 | env_file: 69 | - ${ENV_FILE:-docker-compose-env-files/empty.env} 70 | build: 71 | context: ./customers-service/ 72 | args: 73 | baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION} 74 | image: eventuateexamples/eventuate-examples-java-customers-and-orders-customer-service 75 | ports: 76 | - "8081:8080" 77 | depends_on: 78 | - mysql 79 | - kafka 80 | - zookeeper 81 | environment: 82 | SPRING_DATASOURCE_URL: jdbc:mysql://mysql/eventuate 83 | SPRING_DATASOURCE_USERNAME: mysqluser 84 | SPRING_DATASOURCE_PASSWORD: mysqlpw 85 | SPRING_DATASOURCE_DRIVER_CLASS_NAME: com.mysql.jdbc.Driver 86 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092 87 | EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181 88 | 89 | orders-history-view-service: 90 | env_file: 91 | - ${ENV_FILE:-docker-compose-env-files/empty.env} 92 | build: 93 | context: ./orders-history-view-service/ 94 | args: 95 | baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION} 96 | image: eventuateexamples/eventuate-examples-java-customers-and-orders-order-history-service 97 | ports: 98 | - "8082:8080" 99 | depends_on: 100 | - mongodb 101 | - mysql 102 | - kafka 103 | - zookeeper 104 | environment: 105 | SPRING_DATA_MONGODB_URI: mongodb://mongodb/customers_and_orders 106 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092 107 | EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181 108 | 109 | orders-service: 110 | env_file: 111 | - ${ENV_FILE:-docker-compose-env-files/empty.env} 112 | build: 113 | context: ./orders-service/ 114 | args: 115 | baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION} 116 | image: eventuateexamples/eventuate-examples-java-customers-and-orders-order-service 117 | ports: 118 | - "8083:8080" 119 | depends_on: 120 | - mysql 121 | - kafka 122 | - zookeeper 123 | - customers-service 124 | environment: 125 | CUSTOMER_SERVICE_URL: http://customers-service:8080/customers/{customerId} 126 | ENDPOINTS_SENSITIVE: "false" 127 | SPRING_DATASOURCE_URL: jdbc:mysql://mysql/eventuate 128 | SPRING_DATASOURCE_USERNAME: mysqluser 129 | SPRING_DATASOURCE_PASSWORD: mysqlpw 130 | SPRING_DATASOURCE_DRIVER_CLASS_NAME: com.mysql.jdbc.Driver 131 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092 132 | EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181 133 | -------------------------------------------------------------------------------- /docker-compose-eventuate-local-postgres-polling.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | zookeeper: 4 | image: eventuateio/eventuate-zookeeper:$EVENTUATE_COMMON_VERSION 5 | ports: 6 | - 2181:2181 7 | environment: 8 | ZOOKEEPER_CLIENT_PORT: 2181 9 | KAFKA_HEAP_OPTS: -Xmx64m 10 | 11 | kafka: 12 | image: "eventuateio/eventuate-kafka:$EVENTUATE_MESSAGING_KAFKA_IMAGE_VERSION" 13 | ports: 14 | - 9092:9092 15 | depends_on: 16 | - zookeeper 17 | environment: 18 | KAFKA_LISTENERS: LC://kafka:29092,LX://kafka:9092 19 | KAFKA_ADVERTISED_LISTENERS: LC://kafka:29092,LX://${DOCKER_HOST_IP:-localhost}:9092 20 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LC:PLAINTEXT,LX:PLAINTEXT 21 | KAFKA_INTER_BROKER_LISTENER_NAME: LC 22 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 23 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 24 | KAFKA_HEAP_OPTS: -Xmx192m 25 | 26 | postgres: 27 | image: eventuateio/eventuate-postgres:$EVENTUATE_COMMON_VERSION 28 | ports: 29 | - 5432:5432 30 | environment: 31 | POSTGRES_USER: eventuate 32 | POSTGRES_PASSWORD: eventuate 33 | 34 | 35 | cdc-service: 36 | image: eventuateio/eventuate-cdc-service:$EVENTUATE_CDC_VERSION 37 | ports: 38 | - "8099:8080" 39 | depends_on: 40 | - postgres 41 | - kafka 42 | - zookeeper 43 | environment: 44 | SPRING_DATASOURCE_URL: jdbc:postgresql://postgres/eventuate 45 | SPRING_DATASOURCE_USERNAME: eventuate 46 | SPRING_DATASOURCE_PASSWORD: eventuate 47 | SPRING_DATASOURCE_TEST_ON_BORROW: "true" 48 | SPRING_DATASOURCE_VALIDATION_QUERY: SELECT 1 49 | SPRING_DATASOURCE_DRIVER_CLASS_NAME: org.postgresql.Driver 50 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092 51 | EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181 52 | SPRING_PROFILES_ACTIVE: EventuatePolling 53 | EVENTUATELOCAL_CDC_READER_NAME: PostgresPollingReader 54 | EVENTUATE_CDC_TYPE: "EventuateLocal" 55 | EVENTUATE_OUTBOX_ID: 1 56 | JAVA_OPTS: -Xmx64m 57 | 58 | mongodb: 59 | image: mongo:3.6 60 | hostname: mongodb 61 | command: mongod --smallfiles 62 | ports: 63 | - "27017:27017" 64 | 65 | customers-service: 66 | env_file: 67 | - ${ENV_FILE:-docker-compose-env-files/empty.env} 68 | build: 69 | context: ./customers-service/ 70 | args: 71 | baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION} 72 | image: eventuateexamples/eventuate-examples-java-customers-and-orders-customer-service 73 | ports: 74 | - "8081:8080" 75 | depends_on: 76 | - postgres 77 | - kafka 78 | - zookeeper 79 | environment: 80 | SPRING_DATASOURCE_URL: jdbc:postgresql://postgres/eventuate 81 | SPRING_DATASOURCE_USERNAME: eventuate 82 | SPRING_DATASOURCE_PASSWORD: eventuate 83 | SPRING_DATASOURCE_DRIVER_CLASS_NAME: org.postgresql.Driver 84 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092 85 | EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181 86 | SPRING_PROFILES_ACTIVE: EventuatePolling 87 | EVENTUATELOCAL_CDC_READER_NAME: PostgresPollingReader 88 | 89 | orders-history-view-service: 90 | env_file: 91 | - ${ENV_FILE:-docker-compose-env-files/empty.env} 92 | build: 93 | context: ./orders-history-view-service/ 94 | args: 95 | baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION} 96 | image: eventuateexamples/eventuate-examples-java-customers-and-orders-order-history-service 97 | ports: 98 | - "8082:8080" 99 | depends_on: 100 | - mongodb 101 | - postgres 102 | - kafka 103 | - zookeeper 104 | environment: 105 | SPRING_DATA_MONGODB_URI: mongodb://mongodb/customers_and_orders 106 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092 107 | EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181 108 | 109 | orders-service: 110 | env_file: 111 | - ${ENV_FILE:-docker-compose-env-files/empty.env} 112 | build: 113 | context: ./orders-service/ 114 | args: 115 | baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION} 116 | image: eventuateexamples/eventuate-examples-java-customers-and-orders-order-service 117 | ports: 118 | - "8083:8080" 119 | depends_on: 120 | - postgres 121 | - kafka 122 | - zookeeper 123 | - customers-service 124 | environment: 125 | CUSTOMER_SERVICE_URL: http://customers-service:8080/customers/{customerId} 126 | ENDPOINTS_SENSITIVE: "false" 127 | SPRING_DATASOURCE_URL: jdbc:postgresql://postgres/eventuate 128 | SPRING_DATASOURCE_USERNAME: eventuate 129 | SPRING_DATASOURCE_PASSWORD: eventuate 130 | SPRING_DATASOURCE_DRIVER_CLASS_NAME: org.postgresql.Driver 131 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092 132 | EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181 133 | 134 | -------------------------------------------------------------------------------- /docker-compose-eventuate-local-postgres-wal.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | zookeeper: 4 | image: eventuateio/eventuate-zookeeper:$EVENTUATE_COMMON_VERSION 5 | ports: 6 | - 2181:2181 7 | environment: 8 | ZOOKEEPER_CLIENT_PORT: 2181 9 | KAFKA_HEAP_OPTS: -Xmx64m 10 | 11 | kafka: 12 | image: "eventuateio/eventuate-kafka:$EVENTUATE_MESSAGING_KAFKA_IMAGE_VERSION" 13 | ports: 14 | - 9092:9092 15 | depends_on: 16 | - zookeeper 17 | environment: 18 | KAFKA_LISTENERS: LC://kafka:29092,LX://kafka:9092 19 | KAFKA_ADVERTISED_LISTENERS: LC://kafka:29092,LX://${DOCKER_HOST_IP:-localhost}:9092 20 | KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LC:PLAINTEXT,LX:PLAINTEXT 21 | KAFKA_INTER_BROKER_LISTENER_NAME: LC 22 | KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 23 | KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 24 | KAFKA_HEAP_OPTS: -Xmx192m 25 | 26 | postgres: 27 | image: eventuateio/eventuate-postgres:$EVENTUATE_COMMON_VERSION 28 | ports: 29 | - 5432:5432 30 | environment: 31 | POSTGRES_USER: eventuate 32 | POSTGRES_PASSWORD: eventuate 33 | 34 | 35 | cdc-service: 36 | image: eventuateio/eventuate-cdc-service:$EVENTUATE_CDC_VERSION 37 | ports: 38 | - "8099:8080" 39 | depends_on: 40 | - postgres 41 | - kafka 42 | - zookeeper 43 | environment: 44 | SPRING_DATASOURCE_URL: jdbc:postgresql://postgres/eventuate 45 | SPRING_DATASOURCE_USERNAME: eventuate 46 | SPRING_DATASOURCE_PASSWORD: eventuate 47 | SPRING_DATASOURCE_TEST_ON_BORROW: "true" 48 | SPRING_DATASOURCE_VALIDATION_QUERY: SELECT 1 49 | SPRING_DATASOURCE_DRIVER_CLASS_NAME: org.postgresql.Driver 50 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092 51 | EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181 52 | SPRING_PROFILES_ACTIVE: PostgresWal 53 | EVENTUATELOCAL_CDC_READER_NAME: PostgresWalReader 54 | EVENTUATE_CDC_TYPE: "EventuateLocal" 55 | EVENTUATE_OUTBOX_ID: 1 56 | JAVA_OPTS: -Xmx64m 57 | 58 | mongodb: 59 | image: mongo:3.6 60 | hostname: mongodb 61 | command: mongod --smallfiles 62 | ports: 63 | - "27017:27017" 64 | 65 | customers-service: 66 | env_file: 67 | - ${ENV_FILE:-docker-compose-env-files/empty.env} 68 | build: 69 | context: ./customers-service/ 70 | args: 71 | baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION} 72 | image: eventuateexamples/eventuate-examples-java-customers-and-orders-customer-service 73 | ports: 74 | - "8081:8080" 75 | depends_on: 76 | - postgres 77 | - kafka 78 | - zookeeper 79 | environment: 80 | SPRING_DATASOURCE_URL: jdbc:postgresql://postgres/eventuate 81 | SPRING_DATASOURCE_USERNAME: eventuate 82 | SPRING_DATASOURCE_PASSWORD: eventuate 83 | SPRING_DATASOURCE_DRIVER_CLASS_NAME: org.postgresql.Driver 84 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092 85 | EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181 86 | 87 | orders-history-view-service: 88 | env_file: 89 | - ${ENV_FILE:-docker-compose-env-files/empty.env} 90 | build: 91 | context: ./orders-history-view-service/ 92 | args: 93 | baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION} 94 | image: eventuateexamples/eventuate-examples-java-customers-and-orders-order-history-service 95 | ports: 96 | - "8082:8080" 97 | depends_on: 98 | - mongodb 99 | - postgres 100 | - kafka 101 | - zookeeper 102 | environment: 103 | SPRING_DATA_MONGODB_URI: mongodb://mongodb/customers_and_orders 104 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092 105 | EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181 106 | 107 | orders-service: 108 | env_file: 109 | - ${ENV_FILE:-docker-compose-env-files/empty.env} 110 | build: 111 | context: ./orders-service/ 112 | args: 113 | baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION} 114 | image: eventuateexamples/eventuate-examples-java-customers-and-orders-order-service 115 | ports: 116 | - "8083:8080" 117 | depends_on: 118 | - postgres 119 | - kafka 120 | - zookeeper 121 | - customers-service 122 | environment: 123 | CUSTOMER_SERVICE_URL: http://customers-service:8080/customers/{customerId} 124 | ENDPOINTS_SENSITIVE: "false" 125 | SPRING_DATASOURCE_URL: jdbc:postgresql://postgres/eventuate 126 | SPRING_DATASOURCE_USERNAME: eventuate 127 | SPRING_DATASOURCE_PASSWORD: eventuate 128 | SPRING_DATASOURCE_DRIVER_CLASS_NAME: org.postgresql.Driver 129 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092 130 | EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181 131 | -------------------------------------------------------------------------------- /e2e-test/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | testCompile project(":common-test") 3 | testCompile project(":customers-service-api-web") 4 | testCompile project(":orders-service-api-web") 5 | 6 | testCompile "junit:junit:4.12" 7 | testCompile "org.springframework.boot:spring-boot-starter-web:$springBootVersion" 8 | testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" 9 | } 10 | 11 | test { 12 | ignoreFailures (!project.hasProperty("ignoreE2EFailures") || ignoreE2EFailures.toBoolean()) 13 | } 14 | -------------------------------------------------------------------------------- /e2e-test/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/e2etests/CustomersAndOrdersE2ETest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.e2etests; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.commontest.AbstractCustomerAndOrdersIntegrationTest; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.webapi.CreateCustomerRequest; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.webapi.CreateCustomerResponse; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.webapi.CreateOrderRequest; 8 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.webapi.CreateOrderResponse; 9 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistory.webapi.CustomerView; 10 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistory.webapi.OrderView; 11 | import org.junit.runner.RunWith; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.beans.factory.annotation.Value; 14 | import org.springframework.boot.test.context.SpringBootTest; 15 | import org.springframework.http.HttpStatus; 16 | import org.springframework.http.ResponseEntity; 17 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 18 | import org.springframework.web.client.HttpClientErrorException; 19 | import org.springframework.web.client.RestTemplate; 20 | 21 | import static org.junit.Assert.assertEquals; 22 | 23 | @RunWith(SpringJUnit4ClassRunner.class) 24 | @SpringBootTest(classes = CustomersAndOrdersE2ETestConfiguration.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) 25 | public class CustomersAndOrdersE2ETest extends AbstractCustomerAndOrdersIntegrationTest { 26 | 27 | @Value("${DOCKER_HOST_IP:localhost}") 28 | private String hostName; 29 | 30 | private String baseUrlCustomers(String path) { 31 | return "http://"+hostName+":8081/" + path; 32 | } 33 | 34 | private String baseUrlOrders(String path) { 35 | return "http://"+hostName+":8083/" + path; 36 | } 37 | 38 | private String baseUrlOrderHistory(String path) { 39 | return "http://"+hostName+":8082/" + path; 40 | } 41 | 42 | 43 | @Autowired 44 | RestTemplate restTemplate; 45 | 46 | private CustomerView getCustomer(String customerId) { 47 | try { 48 | ResponseEntity getCustomer = 49 | restTemplate.getForEntity(baseUrlOrderHistory("customers/" + customerId), CustomerView.class); 50 | assertEquals(HttpStatus.OK, getCustomer.getStatusCode()); 51 | return getCustomer.getBody(); 52 | } catch (HttpClientErrorException e) { 53 | if (e.getStatusCode() == HttpStatus.NOT_FOUND) 54 | throw new RuntimeException("Cannot find customer "+ customerId, e); 55 | else 56 | throw e; 57 | } 58 | } 59 | 60 | 61 | @Override 62 | protected CustomerView getCustomerView(String customerId) { 63 | return getCustomer(customerId); 64 | } 65 | 66 | @Override 67 | protected OrderView getOrderView(String orderId) { 68 | try { 69 | ResponseEntity getCustomer = 70 | restTemplate.getForEntity(baseUrlOrderHistory("orders/" + orderId), OrderView.class); 71 | assertEquals(HttpStatus.OK, getCustomer.getStatusCode()); 72 | return getCustomer.getBody(); 73 | } catch (HttpClientErrorException e) { 74 | if (e.getStatusCode() == HttpStatus.NOT_FOUND) 75 | throw new RuntimeException("Cannot find order "+ orderId, e); 76 | else 77 | throw e; 78 | } 79 | } 80 | 81 | @Override 82 | protected String createOrder(String customerId, Money orderTotal) { 83 | try { 84 | ResponseEntity orderResponse = 85 | restTemplate.postForEntity(baseUrlOrders("orders"), new CreateOrderRequest(customerId, orderTotal), CreateOrderResponse.class); 86 | assertEquals(HttpStatus.OK, orderResponse.getStatusCode()); 87 | return orderResponse.getBody().getOrderId(); 88 | } catch (HttpClientErrorException e) { 89 | switch (e.getStatusCode()) { 90 | case BAD_REQUEST: 91 | throw new IntegrationTestCustomerNotFoundException(e); 92 | default: 93 | throw e; 94 | } 95 | } 96 | } 97 | 98 | @Override 99 | protected String createCustomer(Money creditLimit) { 100 | ResponseEntity customerResponse = 101 | restTemplate.postForEntity(baseUrlCustomers("customers"), new CreateCustomerRequest("Fred", creditLimit), CreateCustomerResponse.class); 102 | 103 | assertEquals(HttpStatus.OK, customerResponse.getStatusCode()); 104 | CreateCustomerResponse r = customerResponse.getBody(); 105 | return r.getCustomerId(); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /e2e-test/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/e2etests/CustomersAndOrdersE2ETestConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.e2etests; 2 | 3 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 4 | import org.springframework.boot.autoconfigure.http.HttpMessageConverters; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.http.converter.HttpMessageConverter; 8 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 9 | import org.springframework.web.client.RestTemplate; 10 | 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | @Configuration 15 | @EnableAutoConfiguration 16 | public class CustomersAndOrdersE2ETestConfiguration { 17 | 18 | @Bean 19 | public RestTemplate restTemplate(HttpMessageConverters converters) { 20 | RestTemplate restTemplate = new RestTemplate(); 21 | HttpMessageConverter httpMessageConverter = converters.getConverters().get(0); 22 | List> httpMessageConverters = Arrays.asList(new MappingJackson2HttpMessageConverter()); 23 | restTemplate.setMessageConverters((List>) httpMessageConverters); 24 | return restTemplate; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | 2 | 3 | deployUrl= 4 | eventuateMavenRepoUrl=https://snapshots.repositories.eventuate.io/repository,file:///Users/cer/.m2/testdeploy 5 | 6 | springBootVersion=2.6.12 7 | 8 | eventuateUtilVersion=0.16.0.RELEASE 9 | eventuateExamplesBaseImageVersion=BUILD-16 10 | 11 | eventuateCommonVersion=0.18.0.RELEASE 12 | 13 | eventuateBomVersion=2023.1.RELEASE 14 | 15 | eventuateCommonImageVersion=0.18.0.RELEASE 16 | eventuateMessagingKafkaImageVersion=0.18.0.RELEASE 17 | eventuateCdcImageVersion=0.16.0.RELEASE 18 | springDocOpenApiUiVersion=1.6.6 19 | springRetryVersion=1.2.4.RELEASE 20 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eventuate-examples/eventuate-examples-java-customers-and-orders/9361f9b8ca88d144f86ec76fab77454e4ff45192/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jun 28 06:48:46 PDT 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /migration-tests/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | testCompile project(":customers-service-api-messaging") 3 | testCompile project(":orders-service-api-messaging") 4 | 5 | 6 | testCompile "org.springframework.boot:spring-boot-starter-jdbc:${springBootVersion}" 7 | testCompile "org.springframework.boot:spring-boot-starter-test:${springBootVersion}" 8 | 9 | testRuntime 'mysql:mysql-connector-java:8.0.21' 10 | testRuntime ('org.postgresql:postgresql:9.4-1200-jdbc41') { 11 | exclude group: "org.slf4j", module: "slf4j-simple" 12 | } 13 | testRuntime 'com.microsoft.sqlserver:mssql-jdbc:7.2.1.jre8' 14 | } 15 | 16 | test { 17 | if (!project.ext.has("verifyDbIdMigration")) { 18 | exclude '**/DbIdMigrationVerificationTest**' 19 | } 20 | 21 | forkEvery 1 22 | } -------------------------------------------------------------------------------- /migration-tests/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/migration/DbIdMigrationVerificationTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.migration; 2 | 3 | import io.eventuate.Event; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.events.CustomerCreatedEvent; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.events.CustomerCreditLimitExceededEvent; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.events.CustomerCreditReservedEvent; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.events.OrderApprovedEvent; 8 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.events.OrderCreatedEvent; 9 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.events.OrderRejectedEvent; 10 | import org.junit.Assert; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 16 | import org.springframework.boot.test.context.SpringBootTest; 17 | import org.springframework.context.annotation.Configuration; 18 | import org.springframework.jdbc.core.JdbcTemplate; 19 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 20 | 21 | import java.util.Arrays; 22 | import java.util.HashMap; 23 | import java.util.List; 24 | import java.util.Map; 25 | 26 | @RunWith(SpringJUnit4ClassRunner.class) 27 | @SpringBootTest(classes = DbIdMigrationVerificationTest.Config.class) 28 | public class DbIdMigrationVerificationTest { 29 | 30 | @Configuration 31 | @EnableAutoConfiguration 32 | public static class Config {} 33 | 34 | @Autowired 35 | private JdbcTemplate jdbcTemplate; 36 | 37 | private final Map> eventTypeRequiredFields = new HashMap<>(); 38 | 39 | @Test 40 | //after first call of e2e tests (before migration), events should have ids, after second call (after migration) don't 41 | public void testThatEventsAreMigrated() { 42 | List> eventsWithEmptyId = 43 | jdbcTemplate.queryForList("select * from eventuate.events where event_type <> 'CDC-IGNORED' and event_id = ''"); 44 | 45 | List> eventsWithNotEmptyId = 46 | jdbcTemplate.queryForList("select * from eventuate.events where event_type <> 'CDC-IGNORED' and event_id <> ''"); 47 | 48 | Assert.assertTrue(eventsWithEmptyId.size() > 0); 49 | Assert.assertEquals(eventsWithEmptyId.size(), eventsWithNotEmptyId.size()); 50 | 51 | assertEventsHaveRequiredFields(eventsWithEmptyId); 52 | assertEventsHaveRequiredFields(eventsWithNotEmptyId); 53 | } 54 | 55 | @Before 56 | public void initializeEventTypeRequiredFields() { 57 | addEventFieldsToEventTypeRequiredFields(CustomerCreatedEvent.class, "name", "creditLimit"); 58 | addEventFieldsToEventTypeRequiredFields(CustomerCreditReservedEvent.class, "orderId", "orderTotal"); 59 | addEventFieldsToEventTypeRequiredFields(CustomerCreditLimitExceededEvent.class, "orderId"); 60 | addEventFieldsToEventTypeRequiredFields(OrderCreatedEvent.class, "orderTotal", "customerId"); 61 | addEventFieldsToEventTypeRequiredFields(OrderApprovedEvent.class, "customerId"); 62 | addEventFieldsToEventTypeRequiredFields(OrderRejectedEvent.class, "customerId"); 63 | } 64 | 65 | private void addEventFieldsToEventTypeRequiredFields(Class eventClass, String... fields) { 66 | eventTypeRequiredFields.put(eventClass.getName(), Arrays.asList(fields)); 67 | } 68 | 69 | private void assertEventsHaveRequiredFields(List> events) { 70 | events.forEach(event -> { 71 | String eventType = (String)event.get("event_type"); 72 | List requiredFields = eventTypeRequiredFields.get(eventType); 73 | String eventData = (String)event.get("event_data"); 74 | 75 | String assertMessageTemplate = "event data \"%s\" of event type \"%s\" should contain \"%s\" field"; 76 | 77 | requiredFields 78 | .forEach(field -> 79 | Assert.assertTrue(String.format(assertMessageTemplate, eventData, eventType, field), 80 | eventData.contains(field))); 81 | }); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /migration-tests/src/test/resources/application-postgres.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:postgresql://${DOCKER_HOST_IP:localhost}/eventuate 2 | spring.datasource.username=eventuate 3 | spring.datasource.password=eventuate 4 | spring.datasource.driver-class-name=org.postgresql.Driver -------------------------------------------------------------------------------- /migration-tests/src/test/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:mysql://${DOCKER_HOST_IP:localhost}/eventuate 2 | spring.datasource.username=mysqluser 3 | spring.datasource.password=mysqlpw 4 | spring.datasource.driver-class-name=com.mysql.jdbc.Driver 5 | -------------------------------------------------------------------------------- /mongodb-cli.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | docker run $* --network=${PWD##*/}_default --rm mongo:3.6 sh -c "exec /usr/bin/mongo --host mongodb customers_and_orders" 4 | -------------------------------------------------------------------------------- /mysql-cli.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | docker run $* \ 4 | --name mysqlterm --network=${PWD##*/}_default --rm \ 5 | -e MYSQL_HOST=mysql \ 6 | mysql/mysql-server:8.0.27-1.2.6-server \ 7 | sh -c 'exec mysql -h"$MYSQL_HOST" -uroot -prootpassword -o eventuate' 8 | -------------------------------------------------------------------------------- /orders-history-view-service-api-web/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile project(":common") 3 | compile "org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion" 4 | 5 | testCompile "junit:junit:4.12" 6 | } 7 | -------------------------------------------------------------------------------- /orders-history-view-service-api-web/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistory/webapi/CustomerView.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistory.webapi; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import org.springframework.data.annotation.Id; 5 | import org.springframework.data.mongodb.core.mapping.Document; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | 10 | @Document 11 | public class CustomerView { 12 | 13 | @Id 14 | private String id; 15 | 16 | 17 | private Map orders = new HashMap<>(); 18 | private String name; 19 | private Money creditLimit; 20 | 21 | public void setId(String id) { 22 | this.id = id; 23 | } 24 | 25 | public String getId() { 26 | return id; 27 | } 28 | 29 | public Map getOrders() { 30 | return orders; 31 | } 32 | 33 | public void setName(String name) { 34 | this.name = name; 35 | } 36 | 37 | public String getName() { 38 | return name; 39 | } 40 | 41 | public void setCreditLimit(Money creditLimit) { 42 | this.creditLimit = creditLimit; 43 | } 44 | 45 | public Money getCreditLimit() { 46 | return creditLimit; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /orders-history-view-service-api-web/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistory/webapi/OrderInfo.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistory.webapi; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.OrderState; 5 | 6 | public class OrderInfo { 7 | 8 | private OrderState state; 9 | private String orderId; 10 | private Money orderTotal; 11 | 12 | 13 | public OrderInfo() { 14 | } 15 | 16 | public OrderInfo(String orderId, Money orderTotal) { 17 | this.orderId = orderId; 18 | this.orderTotal = orderTotal; 19 | this.state = OrderState.CREATED; 20 | } 21 | 22 | public void approve() { 23 | state = OrderState.APPROVED; 24 | } 25 | 26 | public void reject() { 27 | state = OrderState.REJECTED; 28 | } 29 | 30 | public Money getOrderTotal() { 31 | return orderTotal; 32 | } 33 | 34 | public OrderState getState() { 35 | return state; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /orders-history-view-service-api-web/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistory/webapi/OrderView.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistory.webapi; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.OrderState; 5 | import org.springframework.data.annotation.Id; 6 | import org.springframework.data.mongodb.core.mapping.Document; 7 | 8 | @Document 9 | public class OrderView { 10 | 11 | @Id 12 | private String id; 13 | 14 | private OrderState state; 15 | private Money orderTotal; 16 | 17 | 18 | public OrderView() { 19 | } 20 | 21 | public OrderView(String id, Money orderTotal) { 22 | this.id = id; 23 | this.orderTotal = orderTotal; 24 | this.state = OrderState.CREATED; 25 | } 26 | 27 | public Money getOrderTotal() { 28 | return orderTotal; 29 | } 30 | 31 | public OrderState getState() { 32 | return state; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /orders-history-view-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:8u252-slim-buster 2 | CMD java ${JAVA_OPTS} -jar orders-history-view-service.jar 3 | COPY build/libs/orders-history-view-service.jar . -------------------------------------------------------------------------------- /orders-history-view-service/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | dependencies { 6 | classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}" 7 | } 8 | } 9 | 10 | apply plugin: VerifyMongoDBConfigurationPlugin 11 | 12 | apply plugin: 'org.springframework.boot' 13 | 14 | apply plugin: 'docker-compose' 15 | 16 | dockerCompose.isRequiredBy(test) 17 | 18 | dockerCompose { 19 | useComposeFiles = ['docker-compose-integration-tests.yml'] 20 | } 21 | 22 | dependencies { 23 | compile project(":orders-history-view-service-api-web") 24 | 25 | compile project(":orders-service-api-messaging") 26 | compile project(":customers-service-api-messaging") 27 | 28 | compile project(":common-swagger") 29 | 30 | compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion" 31 | compile "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion" 32 | compile "org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion" 33 | compile "org.springframework.retry:spring-retry:$springRetryVersion" 34 | 35 | compile "io.eventuate.local.java:eventuate-local-java-spring-events-starter" 36 | 37 | testCompile "junit:junit:4.12" 38 | testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" 39 | } 40 | -------------------------------------------------------------------------------- /orders-history-view-service/docker-compose-integration-tests.yml: -------------------------------------------------------------------------------- 1 | mongodb: 2 | image: mongo:3.6 3 | hostname: mongodb 4 | command: mongod --smallfiles 5 | ports: 6 | - "27017:27017" 7 | -------------------------------------------------------------------------------- /orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/OrderHistoryViewConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.domain.OrderHistoryViewMongoConfiguration; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.service.OrderHistoryViewService; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.service.OrderHistoryViewWorkflow; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Import; 9 | 10 | @Configuration 11 | @Import(OrderHistoryViewMongoConfiguration.class) 12 | public class OrderHistoryViewConfiguration { 13 | 14 | @Bean 15 | public OrderHistoryViewWorkflow orderHistoryViewWorkflow(OrderHistoryViewService service) { 16 | return new OrderHistoryViewWorkflow(service); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/OrderHistoryViewServiceMain.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.web.OrderHistoryViewWebConfiguration; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 6 | import org.springframework.context.annotation.ComponentScan; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Import; 9 | 10 | @Configuration 11 | @Import({OrderHistoryViewConfiguration.class, OrderHistoryViewWebConfiguration.class, }) 12 | @EnableAutoConfiguration 13 | @ComponentScan 14 | public class OrderHistoryViewServiceMain { 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.run(OrderHistoryViewServiceMain.class, args); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/domain/CustomerViewRepository.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.domain; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistory.webapi.CustomerView; 4 | import org.springframework.data.mongodb.repository.MongoRepository; 5 | 6 | public interface CustomerViewRepository 7 | extends MongoRepository, CustomerViewRepositoryCustom { 8 | } 9 | -------------------------------------------------------------------------------- /orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/domain/CustomerViewRepositoryCustom.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.domain; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.OrderState; 5 | 6 | public interface CustomerViewRepositoryCustom { 7 | 8 | void addCustomer(String customerId, String customerName, Money creditLimit); 9 | 10 | void addOrder(String customerId, String orderId, Money orderTotal); 11 | 12 | void updateOrderState(String customerId, String orderId, OrderState state); 13 | } 14 | -------------------------------------------------------------------------------- /orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/domain/CustomerViewRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.domain; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.OrderState; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistory.webapi.CustomerView; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistory.webapi.OrderInfo; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.domain.CustomerViewRepositoryCustom; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.data.mongodb.core.MongoTemplate; 10 | import org.springframework.data.mongodb.core.query.Query; 11 | import org.springframework.data.mongodb.core.query.Update; 12 | 13 | import static org.springframework.data.mongodb.core.query.Criteria.where; 14 | 15 | public class CustomerViewRepositoryImpl implements CustomerViewRepositoryCustom { 16 | 17 | private MongoTemplate mongoTemplate; 18 | 19 | @Autowired 20 | public CustomerViewRepositoryImpl(MongoTemplate mongoTemplate) { 21 | this.mongoTemplate = mongoTemplate; 22 | } 23 | 24 | @Override 25 | public void addCustomer(String customerId, String customerName, Money creditLimit) { 26 | mongoTemplate.upsert(new Query(where("id").is(customerId)), 27 | new Update().set("name", customerName).set("creditLimit", creditLimit), CustomerView.class); 28 | } 29 | 30 | @Override 31 | public void addOrder(String customerId, String orderId, Money orderTotal) { 32 | mongoTemplate.upsert(new Query(where("id").is(customerId)), 33 | new Update().set("orders." + orderId, new OrderInfo(orderId, orderTotal)), CustomerView.class); 34 | } 35 | 36 | @Override 37 | public void updateOrderState(String customerId, String orderId, OrderState state) { 38 | mongoTemplate.upsert(new Query(where("id").is(customerId)), 39 | new Update().set("orders." + orderId + ".state", state), CustomerView.class); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/domain/OrderHistoryViewMongoConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.domain; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.service.OrderHistoryViewService; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.data.mongodb.repository.config.EnableMongoRepositories; 7 | 8 | @Configuration 9 | @EnableMongoRepositories 10 | public class OrderHistoryViewMongoConfiguration { 11 | @Bean 12 | public OrderHistoryViewService orderHistoryViewService(CustomerViewRepository customerViewRepository, OrderViewRepository orderViewRepository) { 13 | return new OrderHistoryViewService(customerViewRepository, orderViewRepository); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/domain/OrderViewRepository.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.domain; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistory.webapi.OrderView; 4 | import org.springframework.data.mongodb.repository.MongoRepository; 5 | 6 | public interface OrderViewRepository extends MongoRepository, OrderViewRepositoryCustom { 7 | } 8 | -------------------------------------------------------------------------------- /orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/domain/OrderViewRepositoryCustom.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.domain; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.OrderState; 5 | 6 | public interface OrderViewRepositoryCustom { 7 | void addOrder(String orderId, Money orderTotal); 8 | void updateOrderState(String orderId, OrderState state); 9 | } 10 | -------------------------------------------------------------------------------- /orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/domain/OrderViewRepositoryImpl.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.domain; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.OrderState; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistory.webapi.OrderView; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.data.mongodb.core.MongoTemplate; 8 | import org.springframework.data.mongodb.core.query.Query; 9 | import org.springframework.data.mongodb.core.query.Update; 10 | 11 | import static org.springframework.data.mongodb.core.query.Criteria.where; 12 | 13 | public class OrderViewRepositoryImpl implements OrderViewRepositoryCustom { 14 | 15 | private MongoTemplate mongoTemplate; 16 | 17 | @Autowired 18 | public OrderViewRepositoryImpl(MongoTemplate mongoTemplate) { 19 | this.mongoTemplate = mongoTemplate; 20 | } 21 | 22 | @Override 23 | public void addOrder(String orderId, Money orderTotal) { 24 | mongoTemplate.upsert(new Query(where("id").is(orderId)), 25 | new Update().set("state", OrderState.CREATED).set("orderTotal", orderTotal), OrderView.class); 26 | } 27 | 28 | @Override 29 | public void updateOrderState(String orderId, OrderState state) { 30 | mongoTemplate.updateFirst(new Query(where("id").is(orderId)), 31 | new Update().set("state", state), OrderView.class); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/service/OrderHistoryViewService.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.service; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.OrderState; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.domain.CustomerViewRepository; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.domain.OrderViewRepository; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.dao.DuplicateKeyException; 9 | import org.springframework.retry.annotation.Backoff; 10 | import org.springframework.retry.annotation.Retryable; 11 | 12 | public class OrderHistoryViewService { 13 | 14 | private CustomerViewRepository customerViewRepository; 15 | private OrderViewRepository orderViewRepository; 16 | 17 | @Autowired 18 | public OrderHistoryViewService(CustomerViewRepository customerViewRepository, OrderViewRepository orderViewRepository) { 19 | this.customerViewRepository = customerViewRepository; 20 | this.orderViewRepository = orderViewRepository; 21 | } 22 | 23 | @Retryable( 24 | value = { DuplicateKeyException.class }, 25 | maxAttempts = 4, 26 | backoff = @Backoff(delay = 250)) 27 | public void createCustomer(String customerId, String customerName, Money creditLimit) { 28 | customerViewRepository.addCustomer(customerId, customerName, creditLimit); 29 | } 30 | 31 | @Retryable( 32 | value = { DuplicateKeyException.class }, 33 | maxAttempts = 4, 34 | backoff = @Backoff(delay = 250)) 35 | public void addOrder(String customerId, String orderId, Money orderTotal) { 36 | customerViewRepository.addOrder(customerId, orderId, orderTotal); 37 | orderViewRepository.addOrder(orderId, orderTotal); 38 | } 39 | 40 | @Retryable( 41 | value = { DuplicateKeyException.class }, 42 | maxAttempts = 4, 43 | backoff = @Backoff(delay = 250)) 44 | public void approveOrder(String customerId, String orderId) { 45 | customerViewRepository.updateOrderState(customerId, orderId, OrderState.APPROVED); 46 | orderViewRepository.updateOrderState(orderId, OrderState.APPROVED); 47 | } 48 | 49 | @Retryable( 50 | value = { DuplicateKeyException.class }, 51 | maxAttempts = 4, 52 | backoff = @Backoff(delay = 250)) 53 | public void rejectOrder(String customerId, String orderId) { 54 | customerViewRepository.updateOrderState(customerId, orderId, OrderState.REJECTED); 55 | orderViewRepository.updateOrderState(orderId, OrderState.REJECTED); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/service/OrderHistoryViewWorkflow.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.service; 2 | 3 | import io.eventuate.DispatchedEvent; 4 | import io.eventuate.EventHandlerMethod; 5 | import io.eventuate.EventSubscriber; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.events.CustomerCreatedEvent; 8 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.events.OrderApprovedEvent; 9 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.events.OrderCreatedEvent; 10 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.events.OrderRejectedEvent; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | 13 | @EventSubscriber(id = "orderHistoryWorkflow") 14 | public class OrderHistoryViewWorkflow { 15 | 16 | private OrderHistoryViewService orderHistoryViewService; 17 | 18 | 19 | @Autowired 20 | public OrderHistoryViewWorkflow(OrderHistoryViewService orderHistoryViewService) { 21 | this.orderHistoryViewService = orderHistoryViewService; 22 | } 23 | 24 | @EventHandlerMethod 25 | public void createCustomer(DispatchedEvent de) { 26 | String customerId = de.getEntityId(); 27 | orderHistoryViewService.createCustomer(customerId, de.getEvent().getName(), 28 | de.getEvent().getCreditLimit()); 29 | } 30 | 31 | @EventHandlerMethod 32 | public void createOrder(DispatchedEvent de) { 33 | String customerId = de.getEvent().getCustomerId(); 34 | String orderId = de.getEntityId(); 35 | Money orderTotal = de.getEvent().getOrderTotal(); 36 | orderHistoryViewService.addOrder(customerId, orderId, orderTotal); 37 | } 38 | 39 | @EventHandlerMethod 40 | public void orderApproved(DispatchedEvent de) { 41 | String customerId = de.getEvent().getCustomerId(); 42 | String orderId = de.getEntityId(); 43 | orderHistoryViewService.approveOrder(customerId, orderId); } 44 | 45 | @EventHandlerMethod 46 | public void orderRejected(DispatchedEvent de) { 47 | String customerId = de.getEvent().getCustomerId(); 48 | String orderId = de.getEntityId(); 49 | orderHistoryViewService.rejectOrder(customerId, orderId); 50 | } 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/web/OrderHistoryViewWebConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.web; 2 | 3 | import org.springframework.boot.autoconfigure.http.HttpMessageConverters; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.ComponentScan; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.http.converter.HttpMessageConverter; 8 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 9 | 10 | @Configuration 11 | @ComponentScan 12 | public class OrderHistoryViewWebConfiguration { 13 | 14 | @Bean 15 | public HttpMessageConverters customConverters() { 16 | HttpMessageConverter additional = new MappingJackson2HttpMessageConverter(); 17 | return new HttpMessageConverters(additional); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/web/customers/CustomerOrderHistoryController.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.web.customers; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistory.webapi.CustomerView; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.domain.CustomerViewRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | @RestController 14 | public class CustomerOrderHistoryController { 15 | 16 | private final CustomerViewRepository customerViewRepository; 17 | 18 | @Autowired 19 | public CustomerOrderHistoryController(CustomerViewRepository customerViewRepository) { 20 | this.customerViewRepository = customerViewRepository; 21 | } 22 | 23 | @RequestMapping(value="/customers/{customerId}", method= RequestMethod.GET) 24 | public ResponseEntity getCustomer(@PathVariable String customerId) { 25 | return customerViewRepository 26 | .findById(customerId) 27 | .map(customer -> new ResponseEntity<>(customer, HttpStatus.OK)) 28 | .orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND)); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/web/orders/OrderViewController.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.web.orders; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistory.webapi.OrderView; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.domain.OrderViewRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.http.ResponseEntity; 8 | import org.springframework.web.bind.annotation.PathVariable; 9 | import org.springframework.web.bind.annotation.RequestMapping; 10 | import org.springframework.web.bind.annotation.RequestMethod; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | @RestController 14 | public class OrderViewController { 15 | 16 | private OrderViewRepository orderViewRepository; 17 | 18 | @Autowired 19 | public OrderViewController(OrderViewRepository orderViewRepository) { 20 | this.orderViewRepository = orderViewRepository; 21 | } 22 | 23 | 24 | @RequestMapping(value="/orders/{orderId}", method= RequestMethod.GET) 25 | public ResponseEntity getOrder(@PathVariable String orderId) { 26 | return orderViewRepository 27 | .findById(orderId) 28 | .map(order -> new ResponseEntity<>(order, HttpStatus.OK)) 29 | .orElseGet(() -> new ResponseEntity<>(HttpStatus.NOT_FOUND)); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /orders-history-view-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | logging.level.io.eventuate.activity=DEBUG 2 | 3 | eventuatelocal.kafka.bootstrap.servers=${DOCKER_HOST_IP:localhost}:9092 4 | eventuatelocal.zookeeper.connection.string=${DOCKER_HOST_IP:localhost}:2181 5 | spring.data.mongodb.uri=mongodb://${DOCKER_HOST_IP:localhost}/customers_and_orders -------------------------------------------------------------------------------- /orders-history-view-service/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | %d{HH:mm:ss.SSS} %-5level %logger{36} - %msg%n 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /orders-history-view-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/views/orderhistory/CustomerViewRepositoryIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.views.orderhistory; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistory.webapi.CustomerView; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.domain.CustomerViewRepository; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 11 | 12 | import java.util.UUID; 13 | 14 | import static org.junit.Assert.assertEquals; 15 | 16 | @RunWith(SpringJUnit4ClassRunner.class) 17 | @SpringBootTest(classes = OrderHistoryViewServiceTestConfiguration.class, 18 | webEnvironment = SpringBootTest.WebEnvironment.NONE) 19 | public class CustomerViewRepositoryIntegrationTest { 20 | 21 | @Autowired 22 | private CustomerViewRepository customerViewRepository; 23 | 24 | @Test 25 | public void shouldCreateAndFindCustomer() { 26 | 27 | String customerId = UUID.randomUUID().toString(); 28 | Money creditLimit = new Money(2000); 29 | String customerName = "Fred"; 30 | 31 | customerViewRepository.addCustomer(customerId, customerName, creditLimit); 32 | CustomerView customerView = customerViewRepository.findById(customerId).get(); 33 | 34 | assertEquals(customerId, customerView.getId()); 35 | assertEquals(customerName, customerView.getName()); 36 | assertEquals(0, customerView.getOrders().size()); 37 | assertEquals(creditLimit, customerView.getCreditLimit()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /orders-history-view-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/views/orderhistory/OrderHistoryViewServiceTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.views.orderhistory; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.OrderState; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistory.webapi.CustomerView; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistory.webapi.OrderView; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.domain.CustomerViewRepository; 8 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.service.OrderHistoryViewService; 9 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.domain.OrderViewRepository; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 15 | 16 | import java.util.UUID; 17 | 18 | import static org.junit.Assert.assertEquals; 19 | import static org.junit.Assert.assertNotNull; 20 | 21 | @RunWith(SpringJUnit4ClassRunner.class) 22 | @SpringBootTest(classes = OrderHistoryViewServiceTestConfiguration.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) 23 | public class OrderHistoryViewServiceTest { 24 | 25 | @Autowired 26 | private OrderHistoryViewService orderHistoryViewService; 27 | 28 | @Autowired 29 | private CustomerViewRepository customerViewRepository; 30 | 31 | @Autowired 32 | private OrderViewRepository orderViewRepository; 33 | 34 | @Test 35 | public void shouldCreateCustomerAndOrdersEtc() { 36 | String customerId = UUID.randomUUID().toString(); 37 | Money creditLimit = new Money(2000); 38 | String customerName = "Fred"; 39 | 40 | String orderId1 = UUID.randomUUID().toString(); 41 | Money orderTotal1 = new Money(1234); 42 | String orderId2 = UUID.randomUUID().toString(); 43 | Money orderTotal2 = new Money(3000); 44 | 45 | orderHistoryViewService.createCustomer(customerId, customerName, creditLimit); 46 | orderHistoryViewService.addOrder(customerId, orderId1, orderTotal1); 47 | orderHistoryViewService.approveOrder(customerId, orderId1); 48 | 49 | orderHistoryViewService.addOrder(customerId, orderId2, orderTotal2); 50 | orderHistoryViewService.rejectOrder(customerId, orderId2); 51 | 52 | CustomerView customerView = customerViewRepository.findById(customerId).get(); 53 | 54 | 55 | assertEquals(2, customerView.getOrders().size()); 56 | assertNotNull(customerView.getOrders().get(orderId1)); 57 | assertNotNull(customerView.getOrders().get(orderId2)); 58 | assertEquals(orderTotal1, customerView.getOrders().get(orderId1).getOrderTotal()); 59 | assertEquals(OrderState.APPROVED, customerView.getOrders().get(orderId1).getState()); 60 | 61 | assertNotNull(customerView.getOrders().get(orderId2)); 62 | assertEquals(orderTotal2, customerView.getOrders().get(orderId2).getOrderTotal()); 63 | assertEquals(OrderState.REJECTED, customerView.getOrders().get(orderId2).getState()); 64 | 65 | OrderView orderView1 = orderViewRepository.findById(orderId1).get(); 66 | assertEquals(orderTotal1, orderView1.getOrderTotal()); 67 | assertEquals(OrderState.APPROVED, orderView1.getState()); 68 | 69 | OrderView orderView2 = orderViewRepository.findById(orderId2).get(); 70 | assertEquals(orderTotal2, orderView2.getOrderTotal()); 71 | assertEquals(OrderState.REJECTED, orderView2.getState()); 72 | } 73 | 74 | 75 | } -------------------------------------------------------------------------------- /orders-history-view-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/views/orderhistory/OrderHistoryViewServiceTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.views.orderhistory; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.domain.OrderHistoryViewMongoConfiguration; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Import; 7 | 8 | @Configuration 9 | @EnableAutoConfiguration 10 | @Import(OrderHistoryViewMongoConfiguration.class) 11 | public class OrderHistoryViewServiceTestConfiguration { 12 | } 13 | -------------------------------------------------------------------------------- /orders-service-api-messaging/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile project(":common") 3 | 4 | testCompile "junit:junit:4.12" 5 | } 6 | -------------------------------------------------------------------------------- /orders-service-api-messaging/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/orders/events/OrderApprovedEvent.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.orders.events; 2 | 3 | import org.apache.commons.lang.builder.EqualsBuilder; 4 | import org.apache.commons.lang.builder.HashCodeBuilder; 5 | 6 | public class OrderApprovedEvent implements OrderEvent { 7 | 8 | private String customerId; 9 | 10 | private OrderApprovedEvent() { 11 | } 12 | 13 | public OrderApprovedEvent(String customerId) { 14 | this.customerId = customerId; 15 | } 16 | 17 | @Override 18 | public boolean equals(Object obj) { 19 | return EqualsBuilder.reflectionEquals(this, obj); 20 | } 21 | 22 | @Override 23 | public int hashCode() { 24 | return HashCodeBuilder.reflectionHashCode(this); 25 | } 26 | 27 | public String getCustomerId() { 28 | return customerId; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /orders-service-api-messaging/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/orders/events/OrderCreatedEvent.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.orders.events; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import org.apache.commons.lang.builder.EqualsBuilder; 5 | import org.apache.commons.lang.builder.HashCodeBuilder; 6 | 7 | public class OrderCreatedEvent implements OrderEvent { 8 | private Money orderTotal; 9 | private String customerId; 10 | 11 | @Override 12 | public boolean equals(Object obj) { 13 | return EqualsBuilder.reflectionEquals(this, obj); 14 | } 15 | 16 | @Override 17 | public int hashCode() { 18 | return HashCodeBuilder.reflectionHashCode(this); 19 | } 20 | 21 | public OrderCreatedEvent() { 22 | } 23 | 24 | public OrderCreatedEvent(String customerId, Money orderTotal) { 25 | this.customerId = customerId; 26 | this.orderTotal = orderTotal; 27 | } 28 | 29 | public Money getOrderTotal() { 30 | return orderTotal; 31 | } 32 | 33 | public String getCustomerId() { 34 | return customerId; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /orders-service-api-messaging/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/orders/events/OrderEvent.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.orders.events; 2 | 3 | import io.eventuate.Event; 4 | import io.eventuate.EventEntity; 5 | 6 | @EventEntity(entity = "net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain.Order") 7 | public interface OrderEvent extends Event { 8 | } 9 | -------------------------------------------------------------------------------- /orders-service-api-messaging/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/orders/events/OrderRejectedEvent.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.orders.events; 2 | 3 | import org.apache.commons.lang.builder.EqualsBuilder; 4 | import org.apache.commons.lang.builder.HashCodeBuilder; 5 | 6 | public class OrderRejectedEvent implements OrderEvent { 7 | 8 | private String customerId; 9 | 10 | private OrderRejectedEvent() { 11 | } 12 | 13 | public OrderRejectedEvent(String customerId) { 14 | this.customerId = customerId; 15 | } 16 | 17 | @Override 18 | public boolean equals(Object obj) { 19 | return EqualsBuilder.reflectionEquals(this, obj); 20 | } 21 | 22 | @Override 23 | public int hashCode() { 24 | return HashCodeBuilder.reflectionHashCode(this); 25 | } 26 | 27 | public String getCustomerId() { 28 | return customerId; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /orders-service-api-web/build.gradle: -------------------------------------------------------------------------------- 1 | dependencies { 2 | compile project(":common") 3 | 4 | testCompile "junit:junit:4.12" 5 | } 6 | -------------------------------------------------------------------------------- /orders-service-api-web/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/orders/webapi/CreateOrderRequest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.orders.webapi; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | 5 | public class CreateOrderRequest { 6 | private Money orderTotal; 7 | private String customerId; 8 | 9 | public CreateOrderRequest() { 10 | } 11 | 12 | public CreateOrderRequest(String customerId, Money orderTotal) { 13 | this.customerId = customerId; 14 | this.orderTotal = orderTotal; 15 | } 16 | 17 | public Money getOrderTotal() { 18 | return orderTotal; 19 | } 20 | 21 | public String getCustomerId() { 22 | return customerId; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /orders-service-api-web/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/orders/webapi/CreateOrderResponse.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.orders.webapi; 2 | 3 | 4 | public class CreateOrderResponse { 5 | private String orderId; 6 | 7 | public CreateOrderResponse() { 8 | } 9 | 10 | public CreateOrderResponse(String orderId) { 11 | this.orderId = orderId; 12 | 13 | } 14 | 15 | public String getOrderId() { 16 | return orderId; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /orders-service/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG baseImageVersion 2 | FROM eventuateio/eventuate-examples-docker-images-spring-example-base-image:$baseImageVersion 3 | COPY build/libs/orders-service.jar service.jar -------------------------------------------------------------------------------- /orders-service/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | } 5 | dependencies { 6 | classpath "io.spring.gradle:dependency-management-plugin:0.6.0.RELEASE" 7 | // if using Stub Runner (consumer side) only remove this dependency 8 | classpath "org.springframework.cloud:spring-cloud-contract-gradle-plugin:1.1.1.RELEASE" 9 | classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}" 10 | } 11 | } 12 | 13 | 14 | 15 | apply plugin: "io.spring.dependency-management" 16 | apply plugin: 'docker-compose' 17 | 18 | dockerCompose.isRequiredBy(test) 19 | 20 | dependencyManagement { 21 | imports { 22 | mavenBom 'org.springframework.cloud:spring-cloud-contract-dependencies:2.2.5.RELEASE' 23 | } 24 | } 25 | 26 | apply plugin: 'org.springframework.boot' 27 | 28 | dockerCompose { 29 | environment.put "EVENTUATE_COMMON_VERSION", eventuateCommonImageVersion 30 | environment.put "EVENTUATE_MESSAGING_KAFKA_IMAGE_VERSION", eventuateMessagingKafkaImageVersion 31 | environment.put "EVENTUATE_CDC_VERSION", eventuateCdcImageVersion 32 | environment.put "EVENTUATE_JAVA_BASE_IMAGE_VERSION", eventuateExamplesBaseImageVersion 33 | 34 | projectName = null 35 | 36 | if ("postgres,EventuatePolling".equals(System.getenv("SPRING_PROFILES_ACTIVE"))) 37 | useComposeFiles = ['../docker-compose-eventuate-local-postgres-polling.yml'] 38 | else if ("postgres,PostgresWal".equals(System.getenv("SPRING_PROFILES_ACTIVE"))) 39 | useComposeFiles = ['../docker-compose-eventuate-local-postgres-wal.yml'] 40 | else useComposeFiles = ['../docker-compose-eventuate-local-mysql-binlog.yml'] 41 | 42 | startedServices = ['customers-service'] 43 | } 44 | 45 | dependencies { 46 | compile project(":orders-service-api-web") 47 | compile project(":customers-service-api-messaging") 48 | compile project(":orders-service-api-messaging") 49 | compile project(":common-swagger") 50 | 51 | compile "org.springframework.boot:spring-boot-starter:$springBootVersion" 52 | compile "org.springframework.boot:spring-boot-starter-web:$springBootVersion" 53 | compile "org.springframework.boot:spring-boot-starter-actuator:$springBootVersion" 54 | compile "io.eventuate.local.java:eventuate-local-java-spring-jdbc-starter" 55 | 56 | 57 | testCompile "net.chrisrichardson.eventstore.examples.customersandorders:common-contracts:1.0-SNAPSHOT:stubs" 58 | 59 | testCompile "io.eventuate.local.java:eventuate-client-java-spring-jdbc" 60 | testCompile project(":customers-service-api-web") 61 | testCompile "org.springframework.cloud:spring-cloud-contract-wiremock" 62 | testCompile "org.springframework.cloud:spring-cloud-starter-contract-stub-runner" 63 | testCompile "junit:junit:4.12" 64 | testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" 65 | testCompile "io.eventuate.local.java:eventuate-client-java-spring-jdbc" 66 | testCompile 'com.jayway.restassured:rest-assured:2.3.0' 67 | testCompile "com.jayway.jsonpath:json-path:2.3.0" 68 | } 69 | -------------------------------------------------------------------------------- /orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/OrderConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice; 2 | 3 | import io.eventuate.sync.AggregateRepository; 4 | import io.eventuate.sync.EventuateAggregateStore; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain.Order; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.CustomerService; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.CustomerServiceProxyConfiguration; 8 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.OrderCommand; 9 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.OrderService; 10 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.OrderServiceImpl; 11 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.OrderWorkflow; 12 | import org.springframework.context.annotation.Bean; 13 | import org.springframework.context.annotation.Configuration; 14 | import org.springframework.context.annotation.Import; 15 | 16 | @Configuration 17 | @Import(CustomerServiceProxyConfiguration.class) 18 | public class OrderConfiguration { 19 | 20 | @Bean 21 | public OrderWorkflow orderWorkflow() { 22 | return new OrderWorkflow(); 23 | } 24 | 25 | @Bean 26 | public OrderService orderService(AggregateRepository orderRepository, 27 | CustomerService customerServiceProxy) { 28 | return new OrderServiceImpl(orderRepository, customerServiceProxy); 29 | } 30 | 31 | @Bean 32 | public AggregateRepository orderRepository(EventuateAggregateStore eventStore) { 33 | return new AggregateRepository<>(Order.class, eventStore); 34 | } 35 | 36 | } 37 | 38 | 39 | -------------------------------------------------------------------------------- /orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/OrdersServiceMain.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.web.OrderWebConfiguration; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 6 | import org.springframework.context.annotation.ComponentScan; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Import; 9 | 10 | @Configuration 11 | @Import({OrderConfiguration.class, OrderWebConfiguration.class, }) 12 | @EnableAutoConfiguration 13 | @ComponentScan 14 | public class OrdersServiceMain { 15 | 16 | public static void main(String[] args) { 17 | SpringApplication.run(OrdersServiceMain.class, args); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/domain/ApproveOrderCommand.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.OrderCommand; 4 | 5 | public class ApproveOrderCommand implements OrderCommand { 6 | } 7 | -------------------------------------------------------------------------------- /orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/domain/CreateOrderCommand.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.OrderCommand; 5 | 6 | public class CreateOrderCommand implements OrderCommand { 7 | private final String customerId; 8 | private final Money orderTotal; 9 | 10 | public CreateOrderCommand(String customerId, Money orderTotal) { 11 | this.customerId = customerId; 12 | this.orderTotal = orderTotal; 13 | } 14 | 15 | public String getCustomerId() { 16 | return customerId; 17 | } 18 | 19 | public Money getOrderTotal() { 20 | return orderTotal; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/domain/Customer.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain; 2 | 3 | public class Customer { 4 | } 5 | -------------------------------------------------------------------------------- /orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/domain/Order.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain; 2 | 3 | import io.eventuate.Event; 4 | import io.eventuate.ReflectiveMutableCommandProcessingAggregate; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.events.OrderApprovedEvent; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.events.OrderCreatedEvent; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.events.OrderRejectedEvent; 8 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.OrderState; 9 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.OrderCommand; 10 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.RejectOrderCommand; 11 | 12 | import java.util.List; 13 | 14 | import static io.eventuate.EventUtil.events; 15 | 16 | public class Order 17 | extends ReflectiveMutableCommandProcessingAggregate { 18 | 19 | private OrderState state; 20 | private String customerId; 21 | 22 | public List process(CreateOrderCommand cmd) { 23 | return events(new OrderCreatedEvent(cmd.getCustomerId(), cmd.getOrderTotal())); 24 | } 25 | 26 | public void apply(OrderCreatedEvent event) { 27 | this.state = OrderState.CREATED; 28 | this.customerId = event.getCustomerId(); 29 | } 30 | 31 | public OrderState getState() { 32 | return state; 33 | } 34 | 35 | public List process(ApproveOrderCommand cmd) { 36 | return events(new OrderApprovedEvent(customerId)); 37 | } 38 | 39 | public List process(RejectOrderCommand cmd) { 40 | return events(new OrderRejectedEvent(customerId)); 41 | } 42 | 43 | 44 | public void apply(OrderApprovedEvent event) { 45 | this.state = OrderState.APPROVED; 46 | } 47 | 48 | 49 | public void apply(OrderRejectedEvent event) { 50 | this.state = OrderState.REJECTED; 51 | } 52 | 53 | 54 | } 55 | -------------------------------------------------------------------------------- /orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/service/CustomerNotFoundException.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service; 2 | 3 | public class CustomerNotFoundException extends RuntimeException { 4 | } 5 | -------------------------------------------------------------------------------- /orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/service/CustomerService.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service; 2 | 3 | public interface CustomerService { 4 | void verifyCustomerCustomerId(String customerId); 5 | } 6 | -------------------------------------------------------------------------------- /orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/service/CustomerServiceProxy.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain.Customer; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.CustomerNotFoundException; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.CustomerService; 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.ResponseEntity; 9 | import org.springframework.util.Assert; 10 | import org.springframework.web.client.HttpClientErrorException; 11 | import org.springframework.web.client.RestTemplate; 12 | 13 | public class CustomerServiceProxy implements CustomerService { 14 | 15 | private RestTemplate restTemplate; 16 | 17 | @Value("${customer.service.url}") 18 | private String customerServiceUrl; 19 | 20 | public CustomerServiceProxy(RestTemplate restTemplate) { 21 | this.restTemplate = restTemplate; 22 | } 23 | 24 | public void setCustomerServiceUrl(String url) { 25 | this.customerServiceUrl = url; 26 | } 27 | 28 | @Override 29 | public void verifyCustomerCustomerId(String customerId) { 30 | Assert.notNull(customerServiceUrl, "Should not be null: " + customerServiceUrl); 31 | ResponseEntity result = null; 32 | try { 33 | result = restTemplate.getForEntity(customerServiceUrl, Customer.class, customerId); 34 | } catch (HttpClientErrorException e) { 35 | switch (e.getStatusCode()) { 36 | case NOT_FOUND: 37 | throw new CustomerNotFoundException(); 38 | default: 39 | unrecognizedStatusCode(customerId, e.getStatusCode()); 40 | } 41 | } 42 | switch (result.getStatusCode()) { 43 | case OK: 44 | return; 45 | case NOT_FOUND: 46 | throw new CustomerNotFoundException(); 47 | default: 48 | unrecognizedStatusCode(customerId, result.getStatusCode()); 49 | } 50 | } 51 | 52 | private void unrecognizedStatusCode(String customerId, HttpStatus statusCode) { 53 | throw new RuntimeException(String.format("Unrecognized status code %s when fetching customerId %s", 54 | statusCode, customerId)); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/service/CustomerServiceProxyConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.CustomerService; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.CustomerServiceProxy; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.web.client.RestTemplate; 8 | 9 | @Configuration 10 | public class CustomerServiceProxyConfiguration { 11 | @Bean 12 | public RestTemplate restTemplate() { 13 | return new RestTemplate(); 14 | } 15 | 16 | @Bean 17 | public CustomerService customerServiceProxy(RestTemplate restTemplate) { 18 | return new CustomerServiceProxy(restTemplate); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/service/OrderCommand.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service; 2 | 3 | 4 | import io.eventuate.Command; 5 | 6 | public interface OrderCommand extends Command { 7 | } 8 | -------------------------------------------------------------------------------- /orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/service/OrderService.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service; 2 | 3 | import io.eventuate.EntityWithIdAndVersion; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain.Order; 6 | 7 | public interface OrderService { 8 | 9 | EntityWithIdAndVersion createOrder(String customerId, Money orderTotal); 10 | } 11 | -------------------------------------------------------------------------------- /orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/service/OrderServiceImpl.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service; 2 | 3 | import io.eventuate.sync.AggregateRepository; 4 | import io.eventuate.EntityWithIdAndVersion; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain.CreateOrderCommand; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain.Order; 8 | 9 | public class OrderServiceImpl implements OrderService { 10 | 11 | private final AggregateRepository orderRepository; 12 | private CustomerService customerService; 13 | 14 | public OrderServiceImpl(AggregateRepository orderRepository, CustomerService customerService) { 15 | this.orderRepository = orderRepository; 16 | this.customerService = customerService; 17 | } 18 | 19 | @Override 20 | public EntityWithIdAndVersion 21 | createOrder(String customerId, Money orderTotal) { 22 | customerService.verifyCustomerCustomerId(customerId); 23 | return orderRepository.save(new CreateOrderCommand(customerId, orderTotal)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/service/OrderWorkflow.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service; 2 | 3 | 4 | import io.eventuate.EntityWithIdAndVersion; 5 | import io.eventuate.EventHandlerContext; 6 | import io.eventuate.EventHandlerMethod; 7 | import io.eventuate.EventSubscriber; 8 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.events.CustomerCreditLimitExceededEvent; 9 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.events.CustomerCreditReservedEvent; 10 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain.ApproveOrderCommand; 11 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain.Order; 12 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.RejectOrderCommand; 13 | 14 | import java.util.concurrent.CompletableFuture; 15 | 16 | @EventSubscriber(id="orderWorkflow") 17 | public class OrderWorkflow { 18 | 19 | @EventHandlerMethod 20 | public CompletableFuture> 21 | creditLimitReserved(EventHandlerContext ctx) { 22 | String orderId = ctx.getEvent().getOrderId(); 23 | 24 | return ctx.update(Order.class, orderId, new ApproveOrderCommand()); 25 | } 26 | 27 | @EventHandlerMethod 28 | public CompletableFuture> 29 | creditLimitExceeded(EventHandlerContext ctx) { 30 | String orderId = ctx.getEvent().getOrderId(); 31 | 32 | return ctx.update(Order.class, orderId, new RejectOrderCommand()); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/service/RejectOrderCommand.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service; 2 | 3 | public class RejectOrderCommand implements OrderCommand { 4 | } 5 | -------------------------------------------------------------------------------- /orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/web/OrderController.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.web; 2 | 3 | import io.eventuate.EntityWithIdAndVersion; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.webapi.CreateOrderRequest; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.webapi.CreateOrderResponse; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.CustomerNotFoundException; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain.Order; 8 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.OrderService; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.web.bind.annotation.RequestBody; 13 | import org.springframework.web.bind.annotation.RequestMapping; 14 | import org.springframework.web.bind.annotation.RequestMethod; 15 | import org.springframework.web.bind.annotation.RestController; 16 | 17 | @RestController 18 | public class OrderController { 19 | 20 | private OrderService orderService; 21 | 22 | @Autowired 23 | public OrderController(OrderService orderService) { 24 | this.orderService = orderService; 25 | } 26 | 27 | @RequestMapping(value = "/orders", method = RequestMethod.POST) 28 | public ResponseEntity createOrder(@RequestBody CreateOrderRequest createOrderRequest) { 29 | try { 30 | EntityWithIdAndVersion order = 31 | orderService.createOrder(createOrderRequest.getCustomerId(), createOrderRequest.getOrderTotal()); 32 | return new ResponseEntity<>(new CreateOrderResponse(order.getEntityId()), HttpStatus.OK); 33 | } catch (CustomerNotFoundException e) { 34 | return new ResponseEntity<>(HttpStatus.BAD_REQUEST); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/web/OrderWebConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.web; 2 | 3 | import org.springframework.boot.autoconfigure.http.HttpMessageConverters; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.ComponentScan; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.http.converter.HttpMessageConverter; 8 | import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; 9 | 10 | @Configuration 11 | @ComponentScan 12 | public class OrderWebConfiguration { 13 | 14 | @Bean 15 | public HttpMessageConverters customConverters() { 16 | HttpMessageConverter additional = new MappingJackson2HttpMessageConverter(); 17 | return new HttpMessageConverters(additional); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /orders-service/src/main/resources/application-postgres.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:postgresql://${DOCKER_HOST_IP:localhost}/eventuate 2 | spring.datasource.username=eventuate 3 | spring.datasource.password=eventuate 4 | spring.datasource.driver-class-name=org.postgresql.Driver -------------------------------------------------------------------------------- /orders-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | logging.level.io.eventuate.activity=DEBUG 2 | 3 | spring.jpa.generate-ddl=true 4 | 5 | eventuatelocal.kafka.bootstrap.servers=${DOCKER_HOST_IP:localhost}:9092 6 | eventuatelocal.zookeeper.connection.string=${DOCKER_HOST_IP:localhost}:2181 7 | 8 | spring.datasource.url=jdbc:mysql://${DOCKER_HOST_IP:localhost}/eventuate 9 | spring.datasource.username=mysqluser 10 | spring.datasource.password=mysqlpw 11 | spring.datasource.driver-class-name=com.mysql.jdbc.Driver -------------------------------------------------------------------------------- /orders-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/CustomerServiceProxyIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.webapi.CreateCustomerRequest; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.customers.webapi.CreateCustomerResponse; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.CustomerNotFoundException; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.CustomerServiceProxy; 8 | import org.junit.After; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.http.HttpStatus; 15 | import org.springframework.http.ResponseEntity; 16 | import org.springframework.jdbc.core.JdbcTemplate; 17 | import org.springframework.test.annotation.DirtiesContext; 18 | import org.springframework.test.context.junit4.SpringRunner; 19 | import org.springframework.web.client.RestTemplate; 20 | 21 | import static org.junit.Assert.assertEquals; 22 | 23 | @RunWith(SpringRunner.class) 24 | @SpringBootTest(classes=CustomerServiceProxyIntegrationTestConfiguration.class, 25 | webEnvironment= SpringBootTest.WebEnvironment.NONE, 26 | properties={"customer.service.url=http://${DOCKER_HOST_IP:localhost}:8081/customers/{customerId}"} 27 | ) 28 | @DirtiesContext 29 | public class CustomerServiceProxyIntegrationTest { 30 | 31 | @Value("http://${DOCKER_HOST_IP:localhost}:8081/customers") 32 | private String customerServiceRootUrl; 33 | 34 | @Autowired 35 | private CustomerServiceProxy customerServiceProxy; 36 | 37 | @Autowired 38 | private RestTemplate restTemplate; 39 | 40 | @Autowired 41 | private JdbcTemplate jdbcTemplate; 42 | 43 | @Test 44 | public void shouldVerifyExistingCustomer() { 45 | ResponseEntity response = restTemplate.postForEntity(customerServiceRootUrl, 46 | new CreateCustomerRequest("Fred", new Money(123)), CreateCustomerResponse.class); 47 | assertEquals(HttpStatus.OK, response.getStatusCode()); 48 | customerServiceProxy.verifyCustomerCustomerId(response.getBody().getCustomerId()); 49 | } 50 | 51 | @Test(expected = CustomerNotFoundException.class) 52 | public void shouldRejectNonExistentCustomer() { 53 | customerServiceProxy.verifyCustomerCustomerId("1223232-none"); 54 | } 55 | 56 | @After 57 | public void cleanUp() { 58 | jdbcTemplate.execute("delete from eventuate.events"); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /orders-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/CustomerServiceProxyIntegrationTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.CustomerServiceProxyConfiguration; 4 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.context.annotation.Import; 7 | 8 | @Configuration 9 | @Import(CustomerServiceProxyConfiguration.class) 10 | @EnableAutoConfiguration 11 | public class CustomerServiceProxyIntegrationTestConfiguration { 12 | } 13 | -------------------------------------------------------------------------------- /orders-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/CustomerServiceProxyTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain.Customer; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.CustomerNotFoundException; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.CustomerServiceProxy; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.client.RestTemplate; 11 | 12 | import static org.mockito.Mockito.mock; 13 | import static org.mockito.Mockito.verify; 14 | import static org.mockito.Mockito.when; 15 | 16 | public class CustomerServiceProxyTest { 17 | 18 | public static final String CUSTOMER_SERVICE_URL = "http://mycustomerservice/customers/{customerId}"; 19 | private static final String CUSTOMER_ID = "customer_id"; 20 | private RestTemplate restTemplate; 21 | private CustomerServiceProxy proxy; 22 | 23 | @Before 24 | public void setUp() { 25 | restTemplate = mock(RestTemplate.class); 26 | proxy = new CustomerServiceProxy(restTemplate); 27 | proxy.setCustomerServiceUrl(CUSTOMER_SERVICE_URL); 28 | } 29 | 30 | @Test 31 | public void shouldFindCustomer() { 32 | when(restTemplate.getForEntity(CUSTOMER_SERVICE_URL, Customer.class, CUSTOMER_ID)) 33 | .thenReturn(new ResponseEntity<>(HttpStatus.OK)); 34 | proxy.verifyCustomerCustomerId(CUSTOMER_ID); 35 | verify(restTemplate).getForEntity(CUSTOMER_SERVICE_URL, Customer.class, CUSTOMER_ID); 36 | } 37 | 38 | @Test(expected= CustomerNotFoundException.class) 39 | public void shouldNotFindCustomer() { 40 | when(restTemplate.getForEntity(CUSTOMER_SERVICE_URL, Customer.class, CUSTOMER_ID)) 41 | .thenReturn(new ResponseEntity<>(HttpStatus.NOT_FOUND)); 42 | proxy.verifyCustomerCustomerId(CUSTOMER_ID); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /orders-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/CustomerServiceProxyWireMockBasedIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.CustomerNotFoundException; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.CustomerServiceProxy; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner; 11 | import org.springframework.test.annotation.DirtiesContext; 12 | import org.springframework.test.context.junit4.SpringRunner; 13 | 14 | @RunWith(SpringRunner.class) 15 | @SpringBootTest(classes=CustomerServiceProxyIntegrationTestConfiguration.class, 16 | webEnvironment= SpringBootTest.WebEnvironment.NONE, 17 | properties={"customer.service.url=http://${DOCKER_HOST_IP:localhost}:8080"} 18 | ) 19 | @AutoConfigureStubRunner(ids = 20 | {"net.chrisrichardson.eventstore.examples.customersandorders:common-contracts:+:stubs:8080"} 21 | // repositoryRoot = "file:///Users/cer/.m2/repository", 22 | ) 23 | @DirtiesContext 24 | public class CustomerServiceProxyWireMockBasedIntegrationTest { 25 | 26 | @Autowired 27 | private CustomerServiceProxy customerServiceProxy; 28 | 29 | @Value("${DOCKER_HOST_IP:localhost}") 30 | private String host; 31 | 32 | @Test 33 | public void shouldVerifyExistingCustomer() { 34 | customerServiceProxy.setCustomerServiceUrl(String.format("http://%s:8080/customers/{customerId}", host)); 35 | customerServiceProxy.verifyCustomerCustomerId(TestData.customerId); 36 | } 37 | 38 | @Test(expected = CustomerNotFoundException.class) 39 | public void shouldRejectNonExistentCustomer() { 40 | customerServiceProxy.setCustomerServiceUrl(String.format("http://%s:8080/customers/{customerId}", host)); 41 | customerServiceProxy.verifyCustomerCustomerId(TestData.nonExistentCustomerId); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /orders-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/OrderServiceInProcessComponentTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend; 2 | 3 | import io.eventuate.EntityWithMetadata; 4 | import io.eventuate.sync.EventuateAggregateStore; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.webapi.CreateOrderRequest; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain.Customer; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain.Order; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.http.HttpStatus; 14 | import org.springframework.http.ResponseEntity; 15 | import org.springframework.test.context.junit4.SpringRunner; 16 | import org.springframework.web.client.RestTemplate; 17 | 18 | import static com.jayway.restassured.RestAssured.given; 19 | import static junit.framework.TestCase.assertNotNull; 20 | import static org.mockito.Mockito.verify; 21 | import static org.mockito.Mockito.when; 22 | 23 | @RunWith(SpringRunner.class) 24 | @SpringBootTest(classes= OrderServiceInProcessComponentTestConfiguration.class, 25 | webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT, 26 | properties = {"customer.service.url=http://${DOCKER_HOST_IP:localhost}:8888/customers/{customerId}", 27 | "spring.main.allow-bean-definition-overriding=true"}) 28 | public class OrderServiceInProcessComponentTest { 29 | 30 | @Value("${local.server.port}") 31 | private int port; 32 | 33 | @Value("${DOCKER_HOST_IP:localhost}") 34 | private String host; 35 | 36 | @Autowired 37 | private RestTemplate restTemplate; 38 | 39 | @Autowired 40 | private EventuateAggregateStore aggregateStore; 41 | 42 | private String baseUrl(String path) { 43 | return "http://" + host + ":" + port + path; 44 | } 45 | 46 | @Test 47 | public void shouldCreateOrder() { 48 | String postUrl = baseUrl("/orders"); 49 | 50 | when(restTemplate.getForEntity(String.format("http://%s:8888/customers/{customerId}", host), 51 | Customer.class, TestData.customerId)) 52 | .thenReturn(new ResponseEntity<>(HttpStatus.OK)); 53 | 54 | String orderId = given(). 55 | body(new CreateOrderRequest(TestData.customerId, TestData.orderTotal)). 56 | contentType("application/json"). 57 | when(). 58 | post(postUrl). 59 | then(). 60 | statusCode(200). 61 | extract(). 62 | path("orderId"); 63 | 64 | verify(restTemplate).getForEntity(String.format("http://%s:8888/customers/{customerId}", host), 65 | Customer.class, TestData.customerId); 66 | 67 | assertNotNull(orderId); 68 | 69 | EntityWithMetadata orderWM = aggregateStore.find(Order.class, orderId); 70 | assertNotNull(orderWM.getEntity()); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /orders-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/OrderServiceInProcessComponentTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend; 2 | 3 | import io.eventuate.javaclient.spring.jdbc.EmbeddedTestAggregateStoreConfiguration; 4 | import io.eventuate.local.java.spring.autoconfigure.EventuateDriverAutoConfiguration; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.OrderConfiguration; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.web.OrderWebConfiguration; 7 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.context.annotation.Import; 11 | import org.springframework.context.annotation.Primary; 12 | import org.springframework.web.client.RestTemplate; 13 | 14 | import static org.mockito.Mockito.mock; 15 | 16 | @Configuration 17 | @Import({OrderConfiguration.class, 18 | OrderWebConfiguration.class, 19 | EmbeddedTestAggregateStoreConfiguration.class 20 | }) 21 | @EnableAutoConfiguration(exclude = {EventuateDriverAutoConfiguration.class}) 22 | public class OrderServiceInProcessComponentTestConfiguration { 23 | 24 | @Bean 25 | @Primary 26 | public RestTemplate restTemplate() { 27 | return mock(RestTemplate.class); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /orders-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/OrderServiceOutOfProcessComponentTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend; 2 | 3 | import io.eventuate.EntityWithMetadata; 4 | import io.eventuate.sync.EventuateAggregateStore; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.webapi.CreateOrderRequest; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain.Order; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.CustomerServiceProxy; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.beans.factory.annotation.Value; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.cloud.contract.stubrunner.spring.AutoConfigureStubRunner; 15 | import org.springframework.test.annotation.DirtiesContext; 16 | import org.springframework.test.context.junit4.SpringRunner; 17 | 18 | import static com.jayway.restassured.RestAssured.given; 19 | import static junit.framework.TestCase.assertNotNull; 20 | 21 | @RunWith(SpringRunner.class) 22 | @SpringBootTest(classes= OrderServiceOutOfProcessComponentTestConfiguration.class, 23 | webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT, 24 | properties = "customer.service.url=http://${DOCKER_HOST_IP:localhost}:8888/customers/{customerId}") 25 | @AutoConfigureStubRunner(ids = 26 | {"net.chrisrichardson.eventstore.examples.customersandorders:common-contracts:+:stubs:8080"}) 27 | @DirtiesContext 28 | public class OrderServiceOutOfProcessComponentTest { 29 | 30 | @Value("${local.server.port}") 31 | private int port; 32 | 33 | @Value("${DOCKER_HOST_IP:localhost}") 34 | private String host; 35 | 36 | @Autowired 37 | private EventuateAggregateStore aggregateStore; 38 | 39 | private String baseUrl(String path) { 40 | return "http://"+ host +":" + port + path; 41 | } 42 | 43 | @Autowired 44 | private CustomerServiceProxy customerServiceProxy; 45 | 46 | @Before 47 | public void setup() { 48 | customerServiceProxy.setCustomerServiceUrl(String.format("http://%s:8080/customers/{customerId}", host)); 49 | } 50 | 51 | @Test 52 | public void shouldCreateOrder() { 53 | String postUrl = baseUrl("/orders"); 54 | 55 | String orderId = given(). 56 | body(new CreateOrderRequest(TestData.customerId, TestData.orderTotal)). 57 | contentType("application/json"). 58 | when(). 59 | post(postUrl). 60 | then(). 61 | statusCode(200). 62 | extract(). 63 | path("orderId"); 64 | 65 | assertNotNull(orderId); 66 | 67 | EntityWithMetadata orderWM = aggregateStore.find(Order.class, orderId); 68 | assertNotNull(orderWM.getEntity()); 69 | } 70 | 71 | @Test 72 | public void shouldRejectCreateOrder() { 73 | String postUrl = baseUrl("/orders"); 74 | 75 | given(). 76 | body(new CreateOrderRequest(TestData.nonExistentCustomerId, TestData.orderTotal)). 77 | contentType("application/json"). 78 | when(). 79 | post(postUrl). 80 | then(). 81 | statusCode(400); 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /orders-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/OrderServiceOutOfProcessComponentTestConfiguration.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend; 2 | 3 | import io.eventuate.javaclient.spring.jdbc.EmbeddedTestAggregateStoreConfiguration; 4 | import io.eventuate.local.java.spring.autoconfigure.EventuateDriverAutoConfiguration; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.OrderConfiguration; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.web.OrderWebConfiguration; 7 | import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.context.annotation.Import; 10 | 11 | @Configuration 12 | @Import({OrderConfiguration.class, 13 | OrderWebConfiguration.class, 14 | EmbeddedTestAggregateStoreConfiguration.class 15 | }) 16 | @EnableAutoConfiguration(exclude = {EventuateDriverAutoConfiguration.class}) 17 | public class OrderServiceOutOfProcessComponentTestConfiguration { 18 | } 19 | -------------------------------------------------------------------------------- /orders-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/OrderTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend; 2 | 3 | 4 | import io.eventuate.Event; 5 | import io.eventuate.EventUtil; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.events.OrderApprovedEvent; 8 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.events.OrderCreatedEvent; 9 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.events.OrderRejectedEvent; 10 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.OrderState; 11 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain.ApproveOrderCommand; 12 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain.CreateOrderCommand; 13 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain.Order; 14 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.RejectOrderCommand; 15 | import org.junit.Test; 16 | 17 | import java.util.List; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | 21 | public class OrderTest { 22 | 23 | Money orderTotal = new Money(10); 24 | String customerId = "customerID"; 25 | 26 | @Test 27 | public void testCreate() { 28 | Order order = new Order(); 29 | 30 | List events = order.process(new CreateOrderCommand(customerId, orderTotal)); 31 | 32 | assertEquals(EventUtil.events(new OrderCreatedEvent(customerId, orderTotal)), events); 33 | 34 | order.apply((OrderCreatedEvent) events.get(0)); 35 | 36 | assertEquals(OrderState.CREATED, order.getState()); 37 | 38 | } 39 | 40 | @Test 41 | public void testApprove() { 42 | Order order = new Order(); 43 | 44 | order.apply((OrderCreatedEvent) order.process(new CreateOrderCommand(customerId, orderTotal)).get(0)); 45 | 46 | List events = order.process(new ApproveOrderCommand()); 47 | 48 | assertEquals(EventUtil.events(new OrderApprovedEvent(customerId)), events); 49 | 50 | order.apply((OrderApprovedEvent) events.get(0)); 51 | 52 | assertEquals(OrderState.APPROVED, order.getState()); 53 | } 54 | 55 | @Test 56 | public void testReject() { 57 | Order order = new Order(); 58 | 59 | order.apply((OrderCreatedEvent) order.process(new CreateOrderCommand(customerId, orderTotal)).get(0)); 60 | 61 | List events = order.process(new RejectOrderCommand()); 62 | 63 | assertEquals(EventUtil.events(new OrderRejectedEvent(customerId)), events); 64 | 65 | order.apply((OrderRejectedEvent) events.get(0)); 66 | 67 | assertEquals(OrderState.REJECTED, order.getState()); 68 | 69 | } 70 | } -------------------------------------------------------------------------------- /orders-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/TestData.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend; 2 | 3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 4 | 5 | public class TestData { 6 | static String customerId = "1223232"; 7 | static String nonExistentCustomerId = "1223232-none"; 8 | 9 | static Money orderTotal = new Money((int) (System.currentTimeMillis() % 1204)); 10 | } 11 | -------------------------------------------------------------------------------- /orders-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/web/OrderControllerTest.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.web; 2 | 3 | import io.eventuate.common.json.mapper.JSonMapper; 4 | import net.chrisrichardson.eventstore.examples.customersandorders.orders.webapi.CreateOrderRequest; 5 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.service.OrderService; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.springframework.test.web.servlet.MockMvc; 9 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 10 | 11 | import static net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.web.OrderMother.applicationJson; 12 | import static net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.web.OrderMother.createOrderResult; 13 | import static net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.web.OrderMother.customerId; 14 | import static net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.web.OrderMother.orderId; 15 | import static net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.web.OrderMother.orderTotal; 16 | import static org.mockito.Mockito.mock; 17 | import static org.mockito.Mockito.verify; 18 | import static org.mockito.Mockito.verifyNoMoreInteractions; 19 | import static org.mockito.Mockito.when; 20 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 21 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 22 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 23 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 24 | 25 | public class OrderControllerTest { 26 | 27 | private OrderService orderService; 28 | private OrderController orderController; 29 | private MockMvc mockMvc; 30 | 31 | @Before 32 | public void setup() { 33 | this.orderService = mock(OrderService.class); 34 | this.orderController = new OrderController(orderService); 35 | this.mockMvc = MockMvcBuilders.standaloneSetup(orderController).build(); 36 | } 37 | 38 | @Test 39 | public void shouldCreateOrder() throws Exception { 40 | CreateOrderRequest request = new CreateOrderRequest(customerId, orderTotal); 41 | when(orderService.createOrder(customerId, orderTotal)).thenReturn(createOrderResult); 42 | this.mockMvc.perform(post("/orders").content(JSonMapper.toJson(request)) 43 | .contentType(applicationJson).accept(applicationJson)) 44 | .andExpect(status().isOk()) 45 | .andExpect(content().contentType(applicationJson)) 46 | .andExpect(jsonPath("$.orderId").value(orderId)); 47 | verify(orderService).createOrder(customerId, orderTotal); 48 | verifyNoMoreInteractions(orderService); 49 | } 50 | } -------------------------------------------------------------------------------- /orders-service/src/test/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/web/OrderMother.java: -------------------------------------------------------------------------------- 1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.web; 2 | 3 | import io.eventuate.EntityIdAndVersion; 4 | import io.eventuate.EntityWithIdAndVersion; 5 | import io.eventuate.common.id.Int128; 6 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money; 7 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.domain.Order; 8 | import org.springframework.http.MediaType; 9 | 10 | public class OrderMother { 11 | static MediaType applicationJson = MediaType.parseMediaType("application/json;charset=UTF-8"); 12 | static String customerId = "customer_id"; 13 | static Money orderTotal = new Money(1234); 14 | static String orderId = "entity-id"; 15 | static EntityWithIdAndVersion createOrderResult = 16 | new EntityWithIdAndVersion<>( 17 | new EntityIdAndVersion(orderId, new Int128(1, 3)), 18 | new Order()); 19 | } 20 | -------------------------------------------------------------------------------- /postgres-cli.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | docker run $* \ 4 | --name postgresterm --network=${PWD##*/}_default \ 5 | -e POSTGRES_HOST=postgres \ 6 | --rm postgres:9.6.5 \ 7 | sh -c 'export PGPASSWORD=eventuate; exec psql -h "$POSTGRES_HOST" -U eventuate' 8 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include "common" 2 | include "common-test" 3 | include 'common-swagger' 4 | include 'orders-history-view-service-api-web' 5 | include 'customers-service-api-web' 6 | include 'customers-service-api-messaging' 7 | include 'orders-service-api-web' 8 | include 'orders-service-api-messaging' 9 | include 'customers-service' 10 | include 'orders-history-view-service' 11 | include 'orders-service' 12 | 13 | include "e2e-test" 14 | include "migration-tests" 15 | 16 | -------------------------------------------------------------------------------- /show-urls.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash -e 2 | 3 | ./wait-for-services.sh ${DOCKER_HOST_IP:localhost} 8081 8082 8083 4 | 5 | echo The microservices are running 6 | echo You can visit these URLS 7 | echo http://localhost:8081/swagger-ui.html - Customer service 8 | echo http:/localhost:8083/swagger-ui.html - Order Service 9 | echo http://localhost:8082/swagger-ui.html - Order History Service 10 | -------------------------------------------------------------------------------- /wait-for-services.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | done=false 4 | 5 | echo waiting for: $* 6 | 7 | host=${1?} 8 | shift 9 | health_url=${1?} 10 | shift 11 | ports=$* 12 | 13 | if [ -z "$ports" ] ; then 14 | echo no ports 15 | exit 99 16 | fi 17 | 18 | while [[ "$done" = false ]]; do 19 | for port in $ports; do 20 | curl --fail http://${host}:${port}/${health_url} >& /dev/null 21 | if [[ "$?" -eq "0" ]]; then 22 | done=true 23 | else 24 | done=false 25 | break 26 | fi 27 | done 28 | if [[ "$done" = true ]]; then 29 | echo connected 30 | break; 31 | fi 32 | echo -n . 33 | sleep 1 34 | done 35 | --------------------------------------------------------------------------------