├── orders-common
├── build.gradle
└── src
│ └── main
│ └── java
│ └── net
│ └── chrisrichardson
│ └── eventstore
│ └── examples
│ └── customersandorders
│ └── orderscommmon
│ ├── CreateOrderResponse.java
│ └── CreateOrderRequest.java
├── customers-common
├── build.gradle
└── src
│ └── main
│ └── java
│ └── net
│ └── chrisrichardson
│ └── eventstore
│ └── examples
│ └── customersandorders
│ └── customerscommon
│ ├── CreateCustomerResponse.java
│ ├── CreateCustomerRequest.java
│ └── GetCustomerResponse.java
├── orders-history-common
├── build.gradle
└── src
│ └── main
│ └── java
│ └── net
│ └── chrisrichardson
│ └── eventstore
│ └── examples
│ └── customersandorders
│ └── ordershistorycommon
│ ├── OrderView.java
│ ├── CustomerView.java
│ └── OrderInfo.java
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── setenv-circle-ci.sh
├── orders-history-view-service
├── src
│ ├── test
│ │ ├── resources
│ │ │ └── application.yml
│ │ └── java
│ │ │ └── net
│ │ │ └── chrisrichardson
│ │ │ └── eventstore
│ │ │ └── examples
│ │ │ └── customersandorders
│ │ │ └── views
│ │ │ └── orderhistory
│ │ │ ├── CustomerViewRepositoryIntegrationTest.java
│ │ │ └── OrderHistoryViewServiceTest.java
│ └── main
│ │ ├── resources
│ │ └── application.yml
│ │ └── java
│ │ └── net
│ │ └── chrisrichardson
│ │ └── eventstore
│ │ └── examples
│ │ └── customersandorders
│ │ └── ordershistoryviewservice
│ │ ├── OrderHistoryQuerySideService.java
│ │ ├── backend
│ │ ├── OrderHistoryViewBackendFactory.java
│ │ ├── OrderHistoryViewService.java
│ │ ├── OrderViewRepository.java
│ │ ├── AbstractRepository.java
│ │ ├── OrderHistoryViewWorkflow.java
│ │ └── CustomerViewRepository.java
│ │ └── web
│ │ ├── orders
│ │ └── OrderViewController.java
│ │ └── customers
│ │ └── CustomerOrderHistoryController.java
├── docker-compose-integration-tests.yml
├── Dockerfile
└── build.gradle
├── orders-service
├── Dockerfile
├── src
│ ├── main
│ │ ├── java
│ │ │ └── net
│ │ │ │ └── chrisrichardson
│ │ │ │ └── eventstore
│ │ │ │ └── examples
│ │ │ │ └── customersandorders
│ │ │ │ └── ordersservice
│ │ │ │ ├── backend
│ │ │ │ ├── Customer.java
│ │ │ │ ├── ApproveOrderCommand.java
│ │ │ │ ├── RejectOrderCommand.java
│ │ │ │ ├── CustomerNotFoundException.java
│ │ │ │ ├── OrderCommand.java
│ │ │ │ ├── CustomerService.java
│ │ │ │ ├── OrderService.java
│ │ │ │ ├── CustomerServiceProxyFactory.java
│ │ │ │ ├── CreateOrderCommand.java
│ │ │ │ ├── OrderBackendFactory.java
│ │ │ │ ├── OrderServiceImpl.java
│ │ │ │ ├── OrderWorkflow.java
│ │ │ │ ├── Order.java
│ │ │ │ └── CustomerServiceProxy.java
│ │ │ │ ├── OrdersServiceMain.java
│ │ │ │ └── web
│ │ │ │ └── OrderController.java
│ │ └── resources
│ │ │ └── application.yml
│ └── test
│ │ ├── resources
│ │ └── application.yml
│ │ └── java
│ │ └── net
│ │ └── chrisrichardson
│ │ └── eventstore
│ │ └── examples
│ │ └── customersandorders
│ │ └── ordersservice
│ │ └── backend
│ │ ├── TestData.java
│ │ ├── CustomerServiceProxyTest.java
│ │ ├── CustomerServiceProxyIntegrationTest.java
│ │ └── OrderTest.java
└── build.gradle
├── customers-service
├── Dockerfile
├── src
│ ├── test
│ │ ├── resources
│ │ │ └── application.yml
│ │ └── java
│ │ │ └── net
│ │ │ └── chrisrichardson
│ │ │ └── eventstore
│ │ │ └── examples
│ │ │ └── customersandorders
│ │ │ └── customersservice
│ │ │ └── backend
│ │ │ ├── CustomerMother.java
│ │ │ ├── CustomerPersistenceTest.java
│ │ │ ├── CustomerServiceInProcessComponentTest.java
│ │ │ ├── CustomerPropertyTest.java
│ │ │ ├── CustomerServiceTest.java
│ │ │ └── CustomerTest.java
│ └── main
│ │ ├── java
│ │ └── net
│ │ │ └── chrisrichardson
│ │ │ └── eventstore
│ │ │ └── examples
│ │ │ └── customersandorders
│ │ │ └── customersservice
│ │ │ ├── backend
│ │ │ ├── CustomerCommand.java
│ │ │ ├── CustomerService.java
│ │ │ ├── ReservedCreditTracker.java
│ │ │ ├── CreateCustomerCommand.java
│ │ │ ├── ReserveCreditCommand.java
│ │ │ ├── CustomerBackendFactory.java
│ │ │ ├── CustomerWorkflow.java
│ │ │ ├── CustomerServiceImpl.java
│ │ │ └── Customer.java
│ │ │ ├── CustomersServiceMain.java
│ │ │ └── web
│ │ │ └── CustomerController.java
│ │ └── resources
│ │ └── application.yml
└── build.gradle
├── mongodb-cli.sh
├── mysql-cli.sh
├── common
├── src
│ ├── main
│ │ ├── java
│ │ │ └── net
│ │ │ │ └── chrisrichardson
│ │ │ │ └── eventstore
│ │ │ │ └── examples
│ │ │ │ └── customersandorders
│ │ │ │ └── common
│ │ │ │ ├── order
│ │ │ │ ├── OrderState.java
│ │ │ │ ├── OrderEvent.java
│ │ │ │ ├── OrderApprovedEvent.java
│ │ │ │ ├── OrderRejectedEvent.java
│ │ │ │ └── OrderCreatedEvent.java
│ │ │ │ ├── customer
│ │ │ │ ├── CustomerEvent.java
│ │ │ │ ├── CustomerCreditLimitExceededEvent.java
│ │ │ │ ├── CustomerCreatedEvent.java
│ │ │ │ └── CustomerCreditReservedEvent.java
│ │ │ │ └── domain
│ │ │ │ └── Money.java
│ │ └── resources
│ │ │ └── logback.xml
│ └── test
│ │ └── java
│ │ └── net
│ │ └── chrisrichardson
│ │ └── eventstore
│ │ └── examples
│ │ └── customersandorders
│ │ └── common
│ │ └── domain
│ │ ├── MoneyTest.java
│ │ └── MoneyPropertyTest.java
└── build.gradle
├── common-test
├── build.gradle
└── src
│ └── main
│ ├── resources
│ └── logback.xml
│ └── java
│ └── net
│ └── chrisrichardson
│ └── eventstore
│ └── examples
│ └── customersandorders
│ └── commontest
│ └── AbstractCustomerAndOrdersIntegrationTest.java
├── .gitignore
├── settings.gradle
├── .circleci
├── upgrade-docker-compose.sh
├── generate-config.sh
└── config.yml
├── README.md
├── run.sh
├── show-urls.sh
├── wait-for-services.sh
├── set-env-eventuate-local-mysql.sh
├── set-env.sh
├── set-env-eventuate-local-postgres-wal.sh
├── set-env-eventuate-local-postgres-polling.sh
├── e2e-test
├── build.gradle
└── src
│ └── test
│ └── java
│ └── net
│ └── chrisrichardson
│ └── eventstore
│ └── examples
│ └── customersandorders
│ └── e2etests
│ ├── CustomersAndOrdersE2ETestConfiguration.java
│ └── CustomersAndOrdersE2ETest.java
├── gradle.properties
├── LICENSE
├── _build-and-test-all.sh
├── gradlew.bat
├── docker-compose-eventuate-local-postgres-wal.yml
├── docker-compose-eventuate-local-postgres-polling.yml
├── docker-compose-eventuate-local-mysql.yml
└── gradlew
/orders-common/build.gradle:
--------------------------------------------------------------------------------
1 | dependencies {
2 | api project(":common")
3 |
4 | testImplementation "junit:junit:4.12"
5 | }
6 |
--------------------------------------------------------------------------------
/customers-common/build.gradle:
--------------------------------------------------------------------------------
1 | dependencies {
2 | api project(":common")
3 |
4 | testImplementation "junit:junit:4.12"
5 | }
6 |
--------------------------------------------------------------------------------
/orders-history-common/build.gradle:
--------------------------------------------------------------------------------
1 | dependencies {
2 | api project(":common")
3 | testImplementation "junit:junit:4.12"
4 | }
5 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eventuate-examples/eventuate-micronaut-examples-customers-and-orders/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/setenv-circle-ci.sh:
--------------------------------------------------------------------------------
1 |
2 | # Host DNS name doesn't resolve in Docker alpine images
3 |
4 | export DOCKER_HOST_IP=$(hostname -I | sed -e 's/ .*//g')
5 | export TERM=dumb
6 |
--------------------------------------------------------------------------------
/orders-history-view-service/src/test/resources/application.yml:
--------------------------------------------------------------------------------
1 | mongodb:
2 | uri: ${SPRING_DATA_MONGODB_URI}
3 |
4 | eventuatelocal.kafka.bootstrap.servers: localhost:9092
5 |
--------------------------------------------------------------------------------
/orders-history-view-service/docker-compose-integration-tests.yml:
--------------------------------------------------------------------------------
1 | services:
2 | mongodb:
3 | image: mongo:4.2.12
4 | hostname: mongodb
5 | ports:
6 | - "27017:27017"
7 |
--------------------------------------------------------------------------------
/orders-service/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG baseImageVersion
2 | FROM eventuateio/eventuate-examples-docker-images-micronaut-example-base-image:$baseImageVersion
3 | COPY build/libs/orders-service.jar service.jar
4 |
--------------------------------------------------------------------------------
/customers-service/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG baseImageVersion
2 | FROM eventuateio/eventuate-examples-docker-images-micronaut-example-base-image:$baseImageVersion
3 | COPY build/libs/customers-service.jar service.jar
4 |
--------------------------------------------------------------------------------
/mongodb-cli.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | docker run --network eventuateexamplesjavacustomersandorders_default --link eventuateexamplesjavacustomersandorders_mongodb_1:mongodb -i -t mongo:3.6 /usr/bin/mongo --host mongodb
4 |
--------------------------------------------------------------------------------
/orders-history-view-service/Dockerfile:
--------------------------------------------------------------------------------
1 | ARG baseImageVersion
2 | FROM eventuateio/eventuate-examples-docker-images-micronaut-example-base-image:$baseImageVersion
3 | COPY build/libs/orders-history-view-service.jar service.jar
4 |
--------------------------------------------------------------------------------
/orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/Customer.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend;
2 |
3 | public class Customer {
4 | }
5 |
--------------------------------------------------------------------------------
/customers-service/src/test/resources/application.yml:
--------------------------------------------------------------------------------
1 | datasources:
2 | default:
3 | url: jdbc:h2:mem:testdb
4 | username: sa
5 | password: test
6 | driverClassName: org.h2.Driver
7 |
8 | eventuatelocal.kafka.bootstrap.servers: localhost:9092
9 |
--------------------------------------------------------------------------------
/mysql-cli.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash -e
2 |
3 | docker run $* \
4 | --network eventuateexamplesjavacustomersandorders_default \
5 | --name mysqlterm --rm mysql/mysql-server:8.0.27-1.2.6-server \
6 | sh -c 'exec mysql -hmysql -P3306 -uroot -prootpassword eventuate'
7 |
--------------------------------------------------------------------------------
/common/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/common/order/OrderState.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.common.order;
2 |
3 | public enum OrderState {
4 | APPROVED, REJECTED, state, CREATED
5 | }
6 |
--------------------------------------------------------------------------------
/common-test/build.gradle:
--------------------------------------------------------------------------------
1 | dependencies {
2 | api project(":common")
3 | api project(":orders-history-common")
4 |
5 | api "io.reactivex:rxjava:1.1.5"
6 | api "junit:junit:4.12"
7 |
8 | api 'io.eventuate.util:eventuate-util-test:0.1.0.RELEASE'
9 |
10 | }
11 |
--------------------------------------------------------------------------------
/.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
--------------------------------------------------------------------------------
/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-7.6.4-all.zip
7 |
--------------------------------------------------------------------------------
/orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/ApproveOrderCommand.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend;
2 |
3 | public class ApproveOrderCommand implements OrderCommand {
4 | }
5 |
--------------------------------------------------------------------------------
/orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/RejectOrderCommand.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend;
2 |
3 | public class RejectOrderCommand implements OrderCommand {
4 | }
5 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include "common"
2 | include "common-test"
3 | include 'orders-history-common'
4 | include 'customers-common'
5 | include 'orders-common'
6 | include 'customers-service'
7 | include 'orders-history-view-service'
8 | include 'orders-service'
9 |
10 | include "e2e-test"
11 |
12 |
--------------------------------------------------------------------------------
/orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/CustomerNotFoundException.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend;
2 |
3 | public class CustomerNotFoundException extends RuntimeException {
4 | }
5 |
--------------------------------------------------------------------------------
/.circleci/upgrade-docker-compose.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash -e
2 |
3 | docker-compose version
4 | docker version
5 | curl -L https://github.com/docker/compose/releases/download/1.25.4/docker-compose-`uname -s`-`uname -m` > ~/docker-compose
6 | chmod +x ~/docker-compose
7 | sudo mv ~/docker-compose /usr/local/bin/docker-compose
8 |
--------------------------------------------------------------------------------
/orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/OrderCommand.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend;
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/backend/CustomerService.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend;
2 |
3 | public interface CustomerService {
4 | void verifyCustomerCustomerId(String customerId);
5 | }
6 |
--------------------------------------------------------------------------------
/customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/backend/CustomerCommand.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.backend;
2 |
3 | import io.eventuate.Command;
4 |
5 | public interface CustomerCommand extends Command {
6 | }
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # An Eventuate project
2 |
3 |
4 |
5 | This project is part of [Eventuate](http://eventuate.io), which is a microservices collaboration platform.
6 |
7 | # eventuate-micronaut-examples-customers-and-orders
8 | Micronaut version of Eventuate event sourcing-based Customers and Orders
9 |
--------------------------------------------------------------------------------
/run.sh:
--------------------------------------------------------------------------------
1 | docker="./gradlew mysqlbinlogCompose"
2 | dockercdc="./gradlew mysqlbinlogcdcCompose"
3 |
4 |
5 |
6 | ${docker}Down
7 |
8 | ${dockercdc}Build
9 | ${dockercdc}Up
10 |
11 | ./wait-for-services.sh $DOCKER_HOST_IP "8099" "actuator/health"
12 |
13 |
14 | ${docker}Build
15 | ${docker}Up
16 |
17 | ./wait-for-services.sh $DOCKER_HOST_IP "8081 8082 8083" "health"
18 |
--------------------------------------------------------------------------------
/show-urls.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash -e
2 |
3 | ./wait-for-services.sh $DOCKER_HOST_IP 8081 8082 8083
4 |
5 | echo The microservices are running
6 | echo You can visit these URLS
7 | echo http://${DOCKER_HOST_IP?}:8081/swagger-ui/index.html - Customer service
8 | echo http://${DOCKER_HOST_IP?}:8083/swagger-ui/index.html - Order Service
9 | echo http://${DOCKER_HOST_IP?}:8082/swagger-ui/index.html - Order History Service
10 |
--------------------------------------------------------------------------------
/orders-service/src/test/resources/application.yml:
--------------------------------------------------------------------------------
1 | customer:
2 | service:
3 | root:
4 | url: http://${DOCKER_HOST_IP}:8081/customers
5 | url: http://${DOCKER_HOST_IP}:8081/customers/{customerId}
6 | datasources:
7 | default:
8 | url: jdbc:h2:mem:testdb
9 | username: sa
10 | password: test
11 | driverClassName: org.h2.Driver
12 |
13 | eventuatelocal.kafka.bootstrap.servers: localhost:9092
14 |
--------------------------------------------------------------------------------
/common/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/common/order/OrderEvent.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.common.order;
2 |
3 | import io.eventuate.Event;
4 | import io.eventuate.EventEntity;
5 |
6 | @EventEntity(entity = "net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend.Order")
7 | public interface OrderEvent extends Event {
8 | }
9 |
--------------------------------------------------------------------------------
/common/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/common/customer/CustomerEvent.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.common.customer;
2 |
3 |
4 | import io.eventuate.Event;
5 | import io.eventuate.EventEntity;
6 |
7 | @EventEntity(entity = "net.chrisrichardson.eventstore.examples.customersandorders.customersservice.backend.Customer")
8 | public interface CustomerEvent extends Event {
9 | }
10 |
--------------------------------------------------------------------------------
/orders-history-view-service/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | endpoints:
2 | health:
3 | enabled: true
4 | sensitive: false
5 | details-visible: ANONYMOUS
6 | mongodb:
7 | uri: ${SPRING_DATA_MONGODB_URI}
8 | micronaut:
9 | router:
10 | static-resources:
11 | swagger-ui:
12 | paths: classpath:META-INF/swagger-ui
13 | mapping: /swagger-ui/**
14 | swagger:
15 | paths: classpath:META-INF/swagger
16 | mapping: /swagger/**
--------------------------------------------------------------------------------
/wait-for-services.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | host=$1
4 | ports=$2
5 | path=$3
6 |
7 | shift 3
8 |
9 | done=false
10 | while [[ "$done" = false ]]; do
11 | for port in $ports; do
12 | curl --fail http://${host}:${port}/${path} >& /dev/null
13 | if [[ "$?" -eq "0" ]]; then
14 | done=true
15 | else
16 | done=false
17 | break
18 | fi
19 | done
20 | if [[ "$done" = true ]]; then
21 | echo services are started
22 | break;
23 | fi
24 | echo -n .
25 | sleep 1
26 | done
27 |
--------------------------------------------------------------------------------
/orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/OrdersServiceMain.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice;
2 |
3 | import io.micronaut.runtime.Micronaut;
4 | import io.swagger.v3.oas.annotations.OpenAPIDefinition;
5 |
6 | @OpenAPIDefinition
7 | public class OrdersServiceMain {
8 | public static void main(String[] args) {
9 | Micronaut.run(OrdersServiceMain.class, args);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/set-env-eventuate-local-mysql.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | . ./set-env.sh
4 |
5 | export SPRING_DATASOURCE_URL=jdbc:mysql://${DOCKER_HOST_IP}/eventuate
6 | export SPRING_DATASOURCE_USERNAME=mysqluser
7 | export SPRING_DATASOURCE_PASSWORD=mysqlpw
8 | export SPRING_DATASOURCE_DRIVER_CLASS_NAME=com.mysql.jdbc.Driver
9 |
10 | export DB_URL=jdbc:mysql://${DOCKER_HOST_IP}/eventuate
11 | export DB_USERNAME=mysqluser
12 | export DB_PASSWORD=mysqlpw
13 | export DB_DRIVERCLASSNAME=com.mysql.jdbc.Driver
14 |
--------------------------------------------------------------------------------
/orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/OrderService.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend;
2 |
3 | import io.eventuate.EntityWithIdAndVersion;
4 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money;
5 |
6 | public interface OrderService {
7 |
8 | EntityWithIdAndVersion createOrder(String customerId, Money orderTotal);
9 | }
10 |
--------------------------------------------------------------------------------
/customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/CustomersServiceMain.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice;
2 |
3 | import io.micronaut.runtime.Micronaut;
4 | import io.swagger.v3.oas.annotations.OpenAPIDefinition;
5 |
6 | @OpenAPIDefinition
7 | public class CustomersServiceMain {
8 | public static void main(String[] args) {
9 | Micronaut.run(CustomersServiceMain.class);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/orders-common/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/orderscommmon/CreateOrderResponse.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.orderscommmon;
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 |
--------------------------------------------------------------------------------
/set-env.sh:
--------------------------------------------------------------------------------
1 | if [ -z "$DOCKER_HOST_IP" ] ; then
2 | if [ -z "$DOCKER_HOST" ] ; then
3 | export DOCKER_HOST_IP=`hostname`
4 | else
5 | echo using ${DOCKER_HOST?}
6 | XX=${DOCKER_HOST%\:*}
7 | export DOCKER_HOST_IP=${XX#tcp\:\/\/}
8 | fi
9 | fi
10 |
11 | echo DOCKER_HOST_IP is $DOCKER_HOST_IP
12 |
13 | export COMPOSE_HTTP_TIMEOUT=240
14 | export SPRING_DATA_MONGODB_URI=mongodb://$DOCKER_HOST_IP/customers_and_orders
15 | export EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS=${DOCKER_HOST_IP}:9092
16 |
--------------------------------------------------------------------------------
/.circleci/generate-config.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash -e
2 |
3 | cat > generated_config.yml <> generated_config.yml < createCustomer(String name, Money creditLimit);
10 |
11 | EntityWithMetadata findById(String customerId);
12 | }
13 |
--------------------------------------------------------------------------------
/.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.9"
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 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2020 Eventuate, Inc. All rights reserved.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | api 'commons-lang:commons-lang:2.6'
4 |
5 | api "io.eventuate.local.java:eventuate-client-java"
6 |
7 | api "io.eventuate.common:eventuate-common-id:$eventuateCommonVersion"
8 |
9 | testImplementation 'com.pholser:junit-quickcheck-core:0.7'
10 | testImplementation 'com.pholser:junit-quickcheck-generators:0.7'
11 | testImplementation "junit:junit:4.12"
12 | testImplementation "org.springframework.boot:spring-boot-starter-test:$springBootVersion"
13 | testImplementation "io.eventuate.local.java:eventuate-client-java-jdbc"
14 |
15 | api 'jakarta.xml.bind:jakarta.xml.bind-api:2.3.3'
16 | api 'com.sun.xml.bind:jaxb-impl:2.3.3'
17 | }
18 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/backend/ReservedCreditTracker.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.backend;
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 | Money reservedCredit() {
12 | return creditReservations.values().stream().reduce(Money.ZERO, Money::add);
13 | }
14 |
15 | void addReservation(String orderId, Money orderTotal) {
16 | creditReservations.put(orderId, orderTotal);
17 | }
18 | }
--------------------------------------------------------------------------------
/customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/backend/CreateCustomerCommand.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 CreateCustomerCommand implements CustomerCommand {
6 | private final String name;
7 | private final Money creditLimit;
8 |
9 | public CreateCustomerCommand(String name, Money creditLimit) {
10 | this.name = name;
11 | this.creditLimit = creditLimit;
12 | }
13 |
14 | public Money getCreditLimit() {
15 | return creditLimit;
16 | }
17 |
18 | public String getName() {
19 | return name;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/customers-common/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customerscommon/CreateCustomerRequest.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.customerscommon;
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/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/backend/ReserveCreditCommand.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 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 |
--------------------------------------------------------------------------------
/orders-common/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/orderscommmon/CreateOrderRequest.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.orderscommmon;
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/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/CreateOrderCommand.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 CreateOrderCommand implements OrderCommand {
6 | private final String customerId;
7 | private final Money orderTotal;
8 |
9 | public CreateOrderCommand(String customerId, Money orderTotal) {
10 | this.customerId = customerId;
11 | this.orderTotal = orderTotal;
12 | }
13 |
14 | public String getCustomerId() {
15 | return customerId;
16 | }
17 |
18 | public Money getOrderTotal() {
19 | return orderTotal;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/OrderBackendFactory.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend;
2 |
3 | import io.eventuate.sync.AggregateRepository;
4 | import io.eventuate.sync.EventuateAggregateStore;
5 | import io.micronaut.context.annotation.Factory;
6 |
7 | import javax.inject.Singleton;
8 |
9 | @Factory
10 | public class OrderBackendFactory {
11 |
12 | @Singleton
13 | public OrderWorkflow orderWorkflow() {
14 | return new OrderWorkflow();
15 | }
16 |
17 | @Singleton
18 | public AggregateRepository orderRepository(EventuateAggregateStore eventStore) {
19 | return new AggregateRepository<>(Order.class, eventStore);
20 | }
21 |
22 | }
23 |
24 |
25 |
--------------------------------------------------------------------------------
/customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/backend/CustomerBackendFactory.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.backend;
2 |
3 | import io.eventuate.sync.AggregateRepository;
4 | import io.eventuate.sync.EventuateAggregateStore;
5 | import io.micronaut.context.annotation.Factory;
6 |
7 | import javax.inject.Singleton;
8 |
9 | @Factory
10 | public class CustomerBackendFactory {
11 |
12 | @Singleton
13 | public CustomerWorkflow customerWorkflow() {
14 | return new CustomerWorkflow();
15 | }
16 |
17 | @Singleton
18 | public AggregateRepository customerRepository(EventuateAggregateStore eventStore) {
19 | return new AggregateRepository<>(Customer.class, eventStore);
20 | }
21 | }
22 |
23 |
24 |
--------------------------------------------------------------------------------
/common/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/common/order/OrderApprovedEvent.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.common.order;
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 |
--------------------------------------------------------------------------------
/common/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/common/order/OrderRejectedEvent.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.common.order;
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/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/OrderServiceImpl.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend;
2 |
3 | import io.eventuate.sync.AggregateRepository;
4 | import io.eventuate.EntityWithIdAndVersion;
5 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money;
6 |
7 | import javax.inject.Inject;
8 |
9 | public class OrderServiceImpl implements OrderService {
10 |
11 | @Inject
12 | private AggregateRepository orderRepository;
13 |
14 | @Inject
15 | private CustomerService customerService;
16 |
17 | @Override
18 | public EntityWithIdAndVersion createOrder(String customerId, Money orderTotal) {
19 | customerService.verifyCustomerCustomerId(customerId);
20 | return orderRepository.save(new CreateOrderCommand(customerId, orderTotal));
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/common/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/common/customer/CustomerCreditLimitExceededEvent.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.common.customer;
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 |
--------------------------------------------------------------------------------
/orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/backend/OrderHistoryViewBackendFactory.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.backend;
2 |
3 | import com.mongodb.client.MongoClient;
4 | import io.micronaut.context.annotation.Factory;
5 |
6 | import javax.inject.Singleton;
7 |
8 | @Factory
9 | public class OrderHistoryViewBackendFactory {
10 |
11 | @Singleton
12 | public OrderHistoryViewWorkflow orderHistoryViewWorkflow(OrderHistoryViewService service) {
13 | return new OrderHistoryViewWorkflow(service);
14 | }
15 |
16 | @Singleton
17 | public CustomerViewRepository customerViewRepository(MongoClient mongoClient) {
18 | return new CustomerViewRepository(mongoClient);
19 | }
20 |
21 | @Singleton
22 | public OrderViewRepository orderViewRepository(MongoClient mongoClient) {
23 | return new OrderViewRepository(mongoClient);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/orders-history-common/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistorycommon/OrderView.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistorycommon;
2 |
3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money;
4 | import net.chrisrichardson.eventstore.examples.customersandorders.common.order.OrderState;
5 |
6 | public class OrderView {
7 |
8 | private String id;
9 |
10 | private OrderState state;
11 | private Money orderTotal;
12 |
13 |
14 | public OrderView() {
15 | }
16 |
17 | public OrderView(String id, Money orderTotal) {
18 | this.id = id;
19 | this.orderTotal = orderTotal;
20 | this.state = OrderState.CREATED;
21 | }
22 |
23 | public Money getOrderTotal() {
24 | return orderTotal;
25 | }
26 |
27 | public OrderState getState() {
28 | return state;
29 | }
30 |
31 | public void setState(OrderState state) {
32 | this.state = state;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/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 io.micronaut.http.HttpResponse;
4 | import io.micronaut.http.annotation.Controller;
5 | import io.micronaut.http.annotation.Get;
6 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistorycommon.OrderView;
7 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.backend.OrderViewRepository;
8 | import javax.inject.Inject;
9 |
10 | @Controller
11 | public class OrderViewController {
12 |
13 | @Inject
14 | private OrderViewRepository orderViewRepository;
15 |
16 | @Get("/orders/{orderId}")
17 | public HttpResponse getOrder(String orderId) {
18 | return orderViewRepository.findById(orderId).map(HttpResponse::ok).orElseGet(HttpResponse::notFound);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 io.micronaut.http.HttpResponse;
4 | import io.micronaut.http.annotation.Controller;
5 | import io.micronaut.http.annotation.Get;
6 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistorycommon.CustomerView;
7 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.backend.CustomerViewRepository;
8 |
9 | import javax.inject.Inject;
10 |
11 | @Controller
12 | public class CustomerOrderHistoryController {
13 |
14 | @Inject
15 | private CustomerViewRepository customerViewRepository;
16 |
17 | @Get("/customers/{customerId}")
18 | public HttpResponse getCustomer(String customerId) {
19 | return customerViewRepository.findById(customerId).map(HttpResponse::ok).orElseGet(HttpResponse::notFound);
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/orders-history-common/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistorycommon/CustomerView.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistorycommon;
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 CustomerView {
9 |
10 | private String id;
11 |
12 |
13 | private Map orders = new HashMap<>();
14 | private String name;
15 | private Money creditLimit;
16 |
17 | public void setId(String id) {
18 | this.id = id;
19 | }
20 |
21 | public String getId() {
22 | return id;
23 | }
24 |
25 | public Map getOrders() {
26 | return orders;
27 | }
28 |
29 | public void setName(String name) {
30 | this.name = name;
31 | }
32 |
33 | public String getName() {
34 | return name;
35 | }
36 |
37 | public void setCreditLimit(Money creditLimit) {
38 | this.creditLimit = creditLimit;
39 | }
40 |
41 | public Money getCreditLimit() {
42 | return creditLimit;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/common/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/common/customer/CustomerCreatedEvent.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.common.customer;
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 |
--------------------------------------------------------------------------------
/common/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/common/order/OrderCreatedEvent.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.common.order;
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-history-common/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistorycommon/OrderInfo.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistorycommon;
2 |
3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money;
4 | import net.chrisrichardson.eventstore.examples.customersandorders.common.order.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 | public void setState(OrderState state) {
39 | this.state = state;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/common/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/common/customer/CustomerCreditReservedEvent.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.common.customer;
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/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/backend/CustomerWorkflow.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.backend;
2 |
3 | import io.eventuate.*;
4 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money;
5 | import net.chrisrichardson.eventstore.examples.customersandorders.common.order.OrderCreatedEvent;
6 |
7 | import java.util.concurrent.CompletableFuture;
8 |
9 | @EventSubscriber(id = "customerWorkflow")
10 | public class CustomerWorkflow implements Subscriber {
11 |
12 | @EventHandlerMethod
13 | public CompletableFuture> reserveCredit(
14 | EventHandlerContext ctx) {
15 |
16 | System.out.println(String.format("reserveCredit invoked %s", ctx));
17 |
18 | OrderCreatedEvent event = ctx.getEvent();
19 | Money orderTotal = event.getOrderTotal();
20 | String customerId = event.getCustomerId();
21 | String orderId = ctx.getEntityId();
22 |
23 | return ctx.update(Customer.class, customerId, new ReserveCreditCommand(orderTotal, orderId));
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/_build-and-test-all.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | set -e
4 |
5 | docker="./gradlew ${database}${mode}Compose"
6 | dockercdc="./gradlew ${database}${mode}cdcCompose"
7 |
8 |
9 | if [ -z "$SPRING_DATA_MONGODB_URI" ] ; then
10 | export SPRING_DATA_MONGODB_URI=mongodb://${DOCKER_HOST_IP?}/customers_orders
11 | echo Set SPRING_DATA_MONGODB_URI $SPRING_DATA_MONGODB_URI
12 | else
13 | echo Already Set SPRING_DATA_MONGODB_URI=$SPRING_DATA_MONGODB_URI
14 | fi
15 |
16 |
17 | if [ "$1" = "--use-existing" ] ; then
18 | shift;
19 | else
20 | ${docker}Down
21 | fi
22 |
23 | NO_RM=false
24 |
25 | if [ "$1" = "--no-rm" ] ; then
26 | NO_RM=true
27 | shift
28 | fi
29 |
30 | ${dockercdc}Build
31 | ${dockercdc}Up
32 |
33 | ./gradlew --stacktrace $BUILD_AND_TEST_ALL_EXTRA_GRADLE_ARGS $* testClasses
34 | ./gradlew --stacktrace $BUILD_AND_TEST_ALL_EXTRA_GRADLE_ARGS $* build -x :e2e-test:test
35 |
36 | ${docker}Build
37 | ${docker}Up
38 |
39 | ./wait-for-services.sh $DOCKER_HOST_IP "8081 8082 8083" "health"
40 |
41 | set -e
42 |
43 | ./gradlew -a $BUILD_AND_TEST_ALL_EXTRA_GRADLE_ARGS $* :e2e-test:cleanTest :e2e-test:test -P ignoreE2EFailures=false
44 |
45 | if [ $NO_RM = false ] ; then
46 | ${docker}Down
47 | fi
48 |
--------------------------------------------------------------------------------
/customers-common/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customerscommon/GetCustomerResponse.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.customerscommon;
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/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 io.micronaut.test.annotation.MicronautTest;
6 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money;
7 | import org.junit.jupiter.api.Test;
8 |
9 | import javax.inject.Inject;
10 |
11 | @MicronautTest
12 | public class CustomerPersistenceTest {
13 |
14 | @Inject
15 | private AggregateRepository aggregateRepository;
16 |
17 | @Test
18 | public void shouldCreateAndUpdateCustomer() {
19 | EntityWithIdAndVersion cwm = aggregateRepository.save(new CreateCustomerCommand("Fred", new Money(1234)));
20 |
21 | aggregateRepository.update(cwm.getEntityId(), new ReserveCreditCommand(new Money(11), "order-1"));
22 | aggregateRepository.update(cwm.getEntityId(), new ReserveCreditCommand(new Money(11), "order-2"));
23 | aggregateRepository.update(cwm.getEntityId(), new ReserveCreditCommand(new Money(11), "order-3"));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/backend/CustomerServiceImpl.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.backend;
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 |
8 | import javax.inject.Inject;
9 | import javax.inject.Singleton;
10 |
11 | @Singleton
12 | public class CustomerServiceImpl implements CustomerService {
13 |
14 | private AggregateRepository customerRepository;
15 |
16 | @Inject
17 | public CustomerServiceImpl(AggregateRepository customerRepository) {
18 | this.customerRepository = customerRepository;
19 | }
20 |
21 | @Override
22 | public EntityWithIdAndVersion createCustomer(String name, Money creditLimit) {
23 | return customerRepository.save(new CreateCustomerCommand(name, creditLimit));
24 | }
25 |
26 | @Override
27 | public EntityWithMetadata findById(String customerId) {
28 | return customerRepository.find(customerId);
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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.web.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 extends HttpMessageConverter>> httpMessageConverters = Arrays.asList(new MappingJackson2HttpMessageConverter());
23 | restTemplate.setMessageConverters((List>) httpMessageConverters);
24 | return restTemplate;
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/OrderWorkflow.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend;
2 |
3 |
4 | import io.eventuate.*;
5 | import net.chrisrichardson.eventstore.examples.customersandorders.common.customer.CustomerCreditLimitExceededEvent;
6 | import net.chrisrichardson.eventstore.examples.customersandorders.common.customer.CustomerCreditReservedEvent;
7 |
8 | import java.util.concurrent.CompletableFuture;
9 |
10 | @EventSubscriber(id="orderWorkflow")
11 | public class OrderWorkflow implements Subscriber {
12 |
13 | @EventHandlerMethod
14 | public CompletableFuture>
15 | creditLimitReserved(EventHandlerContext ctx) {
16 | System.out.println(String.format("creditLimitReserved invoked %s", ctx));
17 |
18 | String orderId = ctx.getEvent().getOrderId();
19 |
20 | return ctx.update(Order.class, orderId, new ApproveOrderCommand());
21 | }
22 |
23 | @EventHandlerMethod
24 | public CompletableFuture>
25 | creditLimitExceeded(EventHandlerContext ctx) {
26 | System.out.println(String.format("creditLimitExceeded invoked %s", ctx));
27 |
28 | String orderId = ctx.getEvent().getOrderId();
29 |
30 | return ctx.update(Order.class, orderId, new RejectOrderCommand());
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/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 io.micronaut.test.annotation.MicronautTest;
4 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money;
5 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistorycommon.CustomerView;
6 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.backend.CustomerViewRepository;
7 | import org.junit.jupiter.api.Test;
8 |
9 | import javax.inject.Inject;
10 | import java.util.UUID;
11 |
12 | import static org.junit.Assert.assertEquals;
13 |
14 | @MicronautTest
15 | public class CustomerViewRepositoryIntegrationTest {
16 |
17 | @Inject
18 | private CustomerViewRepository customerViewRepository;
19 |
20 | @Test
21 | public void shouldCreateAndFindCustomer() {
22 | String customerId = UUID.randomUUID().toString();
23 | Money creditLimit = new Money(2000);
24 | String customerName = "Fred";
25 |
26 | customerViewRepository.addCustomer(customerId, customerName, creditLimit);
27 | CustomerView customerView = customerViewRepository.findById(customerId).get();
28 |
29 | assertEquals(customerId, customerView.getId());
30 | assertEquals(customerName, customerView.getName());
31 | assertEquals(0, customerView.getOrders().size());
32 | assertEquals(creditLimit, customerView.getCreditLimit());
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/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 io.micronaut.http.HttpResponse;
5 | import io.micronaut.http.annotation.Controller;
6 | import io.micronaut.http.annotation.Post;
7 | import net.chrisrichardson.eventstore.examples.customersandorders.orderscommmon.CreateOrderRequest;
8 | import net.chrisrichardson.eventstore.examples.customersandorders.orderscommmon.CreateOrderResponse;
9 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend.CustomerNotFoundException;
10 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend.Order;
11 | import net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend.OrderService;
12 |
13 | import javax.inject.Inject;
14 |
15 | @Controller
16 | public class OrderController {
17 |
18 | @Inject
19 | private OrderService orderService;
20 |
21 | @Post(value = "/orders")
22 | public HttpResponse createOrder(CreateOrderRequest createOrderRequest) {
23 | try {
24 | EntityWithIdAndVersion order =
25 | orderService.createOrder(createOrderRequest.getCustomerId(), createOrderRequest.getOrderTotal());
26 | return HttpResponse.ok(new CreateOrderResponse(order.getEntityId()));
27 | } catch (CustomerNotFoundException e) {
28 | return HttpResponse.badRequest();
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/backend/OrderHistoryViewService.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.backend;
2 |
3 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money;
4 | import net.chrisrichardson.eventstore.examples.customersandorders.common.order.OrderState;
5 |
6 | import javax.inject.Inject;
7 | import javax.inject.Singleton;
8 |
9 | @Singleton
10 | public class OrderHistoryViewService {
11 |
12 | @Inject
13 | private CustomerViewRepository customerViewRepository;
14 |
15 | @Inject
16 | private OrderViewRepository orderViewRepository;
17 |
18 | public void createCustomer(String customerId, String customerName, Money creditLimit) {
19 | customerViewRepository.addCustomer(customerId, customerName, creditLimit);
20 | }
21 |
22 | public void addOrder(String customerId, String orderId, Money orderTotal) {
23 | customerViewRepository.addOrder(customerId, orderId, orderTotal);
24 | orderViewRepository.addOrder(orderId, orderTotal);
25 | }
26 |
27 | public void approveOrder(String customerId, String orderId) {
28 | customerViewRepository.updateOrderState(customerId, orderId, OrderState.APPROVED);
29 | orderViewRepository.updateOrderState(orderId, OrderState.APPROVED);
30 | }
31 |
32 | public void rejectOrder(String customerId, String orderId) {
33 | customerViewRepository.updateOrderState(customerId, orderId, OrderState.REJECTED);
34 | orderViewRepository.updateOrderState(orderId, OrderState.REJECTED);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/backend/OrderViewRepository.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.backend;
2 |
3 | import com.mongodb.BasicDBObject;
4 | import com.mongodb.client.MongoClient;
5 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money;
6 | import net.chrisrichardson.eventstore.examples.customersandorders.common.order.OrderState;
7 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistorycommon.OrderView;
8 |
9 | import java.util.Optional;
10 |
11 | public class OrderViewRepository extends AbstractRepository {
12 |
13 | public OrderViewRepository(MongoClient mongoClient) {
14 | super(mongoClient, "orders");
15 | }
16 |
17 | public Optional findById(String orderId) {
18 | return findOne(orderId)
19 | .map(orderDocument -> {
20 |
21 | OrderView orderView = new OrderView(orderId, getMoney(orderDocument, "orderTotal"));
22 |
23 | Optional
24 | .ofNullable(orderDocument.getString("state"))
25 | .map(OrderState::valueOf).ifPresent(orderView::setState);
26 |
27 | return orderView;
28 | });
29 | }
30 |
31 | public void addOrder(String orderId, Money orderTotal) {
32 | repeatOnFailure(() -> {
33 | findOneAndUpdate(orderId, new BasicDBObject("orderTotal", orderTotal.getAmount()));
34 | });
35 | }
36 |
37 | public void updateOrderState(String orderId, OrderState state) {
38 | repeatOnFailure(() -> {
39 | findOneAndUpdate(orderId, new BasicDBObject("state", state.name()));
40 | });
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/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 org.junit.Before;
4 | import org.junit.Test;
5 | import org.springframework.http.HttpStatus;
6 | import org.springframework.http.ResponseEntity;
7 | import org.springframework.web.client.RestTemplate;
8 |
9 | import static org.mockito.Mockito.mock;
10 | import static org.mockito.Mockito.verify;
11 | import static org.mockito.Mockito.when;
12 |
13 | public class CustomerServiceProxyTest {
14 |
15 | public static final String CUSTOMER_SERVICE_URL = "http://mycustomerservice/customers/{customerId}";
16 | private static final String CUSTOMER_ID = "customer_id";
17 | private RestTemplate restTemplate;
18 | private CustomerServiceProxy proxy;
19 |
20 | @Before
21 | public void setUp() {
22 | restTemplate = mock(RestTemplate.class);
23 | proxy = new CustomerServiceProxy(restTemplate);
24 | proxy.setCustomerServiceUrl(CUSTOMER_SERVICE_URL);
25 | }
26 |
27 | @Test
28 | public void shouldFindCustomer() {
29 | when(restTemplate.getForEntity(CUSTOMER_SERVICE_URL, Customer.class, CUSTOMER_ID))
30 | .thenReturn(new ResponseEntity<>(HttpStatus.OK));
31 | proxy.verifyCustomerCustomerId(CUSTOMER_ID);
32 | verify(restTemplate).getForEntity(CUSTOMER_SERVICE_URL, Customer.class, CUSTOMER_ID);
33 | }
34 |
35 | @Test(expected=CustomerNotFoundException.class)
36 | public void shouldNotFindCustomer() {
37 | when(restTemplate.getForEntity(CUSTOMER_SERVICE_URL, Customer.class, CUSTOMER_ID))
38 | .thenReturn(new ResponseEntity<>(HttpStatus.NOT_FOUND));
39 | proxy.verifyCustomerCustomerId(CUSTOMER_ID);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/Order.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend;
2 |
3 | import io.eventuate.Event;
4 | import io.eventuate.ReflectiveMutableCommandProcessingAggregate;
5 | import net.chrisrichardson.eventstore.examples.customersandorders.common.order.OrderApprovedEvent;
6 | import net.chrisrichardson.eventstore.examples.customersandorders.common.order.OrderCreatedEvent;
7 | import net.chrisrichardson.eventstore.examples.customersandorders.common.order.OrderRejectedEvent;
8 | import net.chrisrichardson.eventstore.examples.customersandorders.common.order.OrderState;
9 |
10 | import java.util.List;
11 |
12 | import static io.eventuate.EventUtil.events;
13 |
14 | public class Order
15 | extends ReflectiveMutableCommandProcessingAggregate {
16 |
17 | private OrderState state;
18 | private String customerId;
19 |
20 | public List process(CreateOrderCommand cmd) {
21 | return events(new OrderCreatedEvent(cmd.getCustomerId(), cmd.getOrderTotal()));
22 | }
23 |
24 | public void apply(OrderCreatedEvent event) {
25 | this.state = OrderState.CREATED;
26 | this.customerId = event.getCustomerId();
27 | }
28 |
29 | public OrderState getState() {
30 | return state;
31 | }
32 |
33 | public List process(ApproveOrderCommand cmd) {
34 | return events(new OrderApprovedEvent(customerId));
35 | }
36 |
37 | public List process(RejectOrderCommand cmd) {
38 | return events(new OrderRejectedEvent(customerId));
39 | }
40 |
41 |
42 | public void apply(OrderApprovedEvent event) {
43 | this.state = OrderState.APPROVED;
44 | }
45 |
46 |
47 | public void apply(OrderRejectedEvent event) {
48 | this.state = OrderState.REJECTED;
49 | }
50 |
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/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 io.micronaut.context.annotation.Value;
4 | import io.micronaut.test.annotation.MicronautTest;
5 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money;
6 | import net.chrisrichardson.eventstore.examples.customersandorders.customerscommon.CreateCustomerRequest;
7 | import net.chrisrichardson.eventstore.examples.customersandorders.customerscommon.CreateCustomerResponse;
8 | import org.junit.jupiter.api.Assertions;
9 | import org.junit.jupiter.api.Test;
10 | import org.springframework.http.HttpStatus;
11 | import org.springframework.http.ResponseEntity;
12 | import org.springframework.web.client.RestTemplate;
13 |
14 | import javax.inject.Inject;
15 |
16 | import static org.junit.Assert.assertEquals;
17 |
18 | @MicronautTest
19 | public class CustomerServiceProxyIntegrationTest {
20 |
21 | @Inject
22 | private CustomerServiceProxy customerServiceProxy;
23 |
24 | @Inject
25 | private RestTemplate restTemplate;
26 |
27 | @Value("${customer.service.root.url}")
28 | private String customerServiceRootUrl;
29 |
30 | @Test
31 | public void shouldVerifyExistingCustomer() {
32 | ResponseEntity response = restTemplate.postForEntity(customerServiceRootUrl,
33 | new CreateCustomerRequest("Fred", new Money(123)), CreateCustomerResponse.class);
34 | assertEquals(HttpStatus.OK, response.getStatusCode());
35 | customerServiceProxy.verifyCustomerCustomerId(response.getBody().getCustomerId());
36 | }
37 |
38 | @Test
39 | public void shouldRejectNonExistentCustomer() {
40 | Assertions.assertThrows(CustomerNotFoundException.class, () -> customerServiceProxy.verifyCustomerCustomerId("1223232-none"));
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/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 com.jayway.restassured.RestAssured;
4 | import io.micronaut.context.ApplicationContext;
5 | import io.micronaut.runtime.server.EmbeddedServer;
6 | import io.micronaut.test.annotation.MicronautTest;
7 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money;
8 | import net.chrisrichardson.eventstore.examples.customersandorders.customerscommon.CreateCustomerRequest;
9 | import org.junit.jupiter.api.Test;
10 | import static junit.framework.TestCase.assertNotNull;
11 | import static org.junit.Assert.assertEquals;
12 |
13 | @MicronautTest
14 | public class CustomerServiceInProcessComponentTest {
15 |
16 | private String baseUrl(int port, String path) {
17 | return "http://localhost:" + port + path;
18 | }
19 |
20 | @Test
21 | public void shouldCreateOrder() {
22 | EmbeddedServer embeddedServer = ApplicationContext.run(EmbeddedServer.class);
23 |
24 | int port = embeddedServer.getPort();
25 |
26 | String postUrl = baseUrl(port,"/customers");
27 |
28 | String customerId = RestAssured.given().
29 | body(new CreateCustomerRequest("John Doe", new Money(1234))).
30 | contentType("application/json").
31 | when().
32 | post(postUrl).
33 | then().
34 | statusCode(200).
35 | extract().
36 | path("customerId");
37 |
38 | assertNotNull(customerId);
39 |
40 |
41 | Integer creditLimit = RestAssured.given().
42 | when().
43 | get(postUrl + "/" + customerId).
44 | then().
45 | statusCode(200).
46 | extract().
47 | path("creditLimit.amount");
48 |
49 | assertEquals(new Integer(1234), creditLimit);
50 |
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/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.common.customer.CustomerCreatedEvent;
10 | import net.chrisrichardson.eventstore.examples.customersandorders.common.customer.CustomerCreditLimitExceededEvent;
11 | import net.chrisrichardson.eventstore.examples.customersandorders.common.customer.CustomerCreditReservedEvent;
12 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money;
13 | import org.junit.runner.RunWith;
14 |
15 | import java.util.List;
16 |
17 | import static java.util.Collections.singletonList;
18 | import static org.junit.Assert.assertEquals;
19 |
20 | @RunWith(JUnitQuickcheck.class)
21 | public class CustomerPropertyTest {
22 |
23 | @Property
24 | public void reserveCredit(@InRange(minInt = 1) int orderTotal) {
25 | Customer customer = Aggregates.recreateAggregate(Customer.class,
26 | singletonList(new CustomerCreatedEvent(CustomerMother.name, CustomerMother.creditLimit)), DefaultMissingApplyEventMethodStrategy.INSTANCE);
27 |
28 | List events = customer.process(new ReserveCreditCommand(new Money(orderTotal), CustomerMother.orderId));
29 |
30 | assertEquals(1, events.size());
31 | Class extends Event> eventClass = events.get(0).getClass();
32 |
33 | if (CustomerMother.creditLimit.isGreaterThanOrEqual(new Money(orderTotal))) {
34 | assertEquals(CustomerCreditReservedEvent.class, eventClass);
35 | } else
36 | assertEquals(CustomerCreditLimitExceededEvent.class, eventClass);
37 |
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/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 org.junit.jupiter.api.BeforeEach;
6 | import org.junit.jupiter.api.Test;
7 | import org.mockito.ArgumentCaptor;
8 |
9 | import static org.junit.Assert.assertEquals;
10 | import static org.junit.Assert.assertSame;
11 | import static org.mockito.Matchers.any;
12 | import static org.mockito.Mockito.mock;
13 | import static org.mockito.Mockito.verify;
14 | import static org.mockito.Mockito.verifyNoMoreInteractions;
15 | import static org.mockito.Mockito.when;
16 |
17 | public class CustomerServiceTest {
18 |
19 | private CustomerService customerService;
20 | private AggregateRepository aggregateRepository;
21 |
22 | @BeforeEach
23 | public void setUp() {
24 | aggregateRepository = mock(AggregateRepository.class);
25 | customerService = new CustomerServiceImpl(aggregateRepository);
26 | }
27 |
28 | @Test
29 | public void shouldCreateCustomer() {
30 |
31 | EntityWithIdAndVersion returned = new EntityWithIdAndVersion<>(null, null);
32 |
33 | when(aggregateRepository.save(any(CreateCustomerCommand.class))).thenReturn(returned);
34 |
35 | EntityWithIdAndVersion result =
36 | customerService.createCustomer(CustomerMother.name, CustomerMother.creditLimit);
37 |
38 | assertSame(returned, result);
39 |
40 | ArgumentCaptor argument = ArgumentCaptor.forClass(CreateCustomerCommand.class);
41 |
42 | verify(aggregateRepository).save(argument.capture());
43 | verifyNoMoreInteractions(aggregateRepository);
44 |
45 | CreateCustomerCommand command = argument.getValue();
46 |
47 | assertEquals(CustomerMother.name, command.getName());
48 | assertEquals(CustomerMother.creditLimit, command.getCreditLimit());
49 |
50 | }
51 |
52 | }
--------------------------------------------------------------------------------
/orders-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordersservice/backend/CustomerServiceProxy.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.backend;
2 |
3 | import io.micronaut.context.annotation.Value;
4 | import org.springframework.http.HttpStatus;
5 | import org.springframework.http.ResponseEntity;
6 | import org.springframework.util.Assert;
7 | import org.springframework.web.client.HttpClientErrorException;
8 | import org.springframework.web.client.RestTemplate;
9 |
10 | import javax.inject.Inject;
11 | import javax.inject.Singleton;
12 |
13 | @Singleton
14 | public class CustomerServiceProxy implements CustomerService {
15 |
16 | @Value("${customer.service.url}")
17 | private String customerServiceUrl;
18 |
19 | private RestTemplate restTemplate;
20 |
21 | @Inject
22 | public CustomerServiceProxy(RestTemplate restTemplate) {
23 | this.restTemplate = restTemplate;
24 | }
25 |
26 | public void setCustomerServiceUrl(String customerServiceUrl) {
27 | this.customerServiceUrl = customerServiceUrl;
28 | }
29 |
30 | @Override
31 | public void verifyCustomerCustomerId(String customerId) {
32 | Assert.notNull(customerServiceUrl, "Should not be null: " + customerServiceUrl);
33 | ResponseEntity result = null;
34 | try {
35 | result = restTemplate.getForEntity(customerServiceUrl, Customer.class, customerId);
36 | } catch (HttpClientErrorException e) {
37 | switch (e.getStatusCode()) {
38 | case NOT_FOUND:
39 | throw new CustomerNotFoundException();
40 | default:
41 | unrecognizedStatusCode(customerId, e.getStatusCode());
42 | }
43 | }
44 | switch (result.getStatusCode()) {
45 | case OK:
46 | return;
47 | case NOT_FOUND:
48 | throw new CustomerNotFoundException();
49 | default:
50 | unrecognizedStatusCode(customerId, result.getStatusCode());
51 | }
52 | }
53 |
54 | private void unrecognizedStatusCode(String customerId, HttpStatus statusCode) {
55 | throw new RuntimeException(String.format("Unrecognized status code %s when fetching customerId %s",
56 | statusCode, customerId));
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/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 io.micronaut.http.HttpResponse;
7 | import io.micronaut.http.annotation.Controller;
8 | import io.micronaut.http.annotation.Get;
9 | import io.micronaut.http.annotation.Post;
10 | import net.chrisrichardson.eventstore.examples.customersandorders.customerscommon.CreateCustomerRequest;
11 | import net.chrisrichardson.eventstore.examples.customersandorders.customerscommon.CreateCustomerResponse;
12 | import net.chrisrichardson.eventstore.examples.customersandorders.customerscommon.GetCustomerResponse;
13 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.backend.Customer;
14 | import net.chrisrichardson.eventstore.examples.customersandorders.customersservice.backend.CustomerService;
15 |
16 | import javax.inject.Inject;
17 |
18 | @Controller
19 | public class CustomerController {
20 |
21 | @Inject
22 | private CustomerService customerService;
23 |
24 | @Post(value = "/customers")
25 | public CreateCustomerResponse createCustomer(CreateCustomerRequest createCustomerRequest) {
26 | EntityWithIdAndVersion ewidv = customerService.createCustomer(createCustomerRequest.getName(), createCustomerRequest.getCreditLimit());
27 | return new CreateCustomerResponse(ewidv.getEntityId());
28 | }
29 |
30 | @Get("/customers/{customerId}")
31 | public HttpResponse getCustomer(String customerId) {
32 | EntityWithMetadata customerWithMetadata;
33 | try {
34 | customerWithMetadata = customerService.findById(customerId);
35 | } catch (EntityNotFoundException e) {
36 | return HttpResponse.notFound();
37 | }
38 |
39 | Customer customer = customerWithMetadata.getEntity();
40 | GetCustomerResponse response =
41 | new GetCustomerResponse(customerWithMetadata.getEntityIdAndVersion().getEntityId(), customer.getCreditLimit(),
42 | customer.availableCredit());
43 |
44 | return HttpResponse.ok(response);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/customers-service/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "io.spring.dependency-management"
3 | id "java"
4 | id "com.github.johnrengelman.shadow" version "4.0.2"
5 | id "application"
6 | }
7 |
8 | repositories {
9 | mavenCentral()
10 | }
11 |
12 | dependencyManagement {
13 | imports {
14 | mavenBom "io.micronaut:micronaut-bom:$micronautVersion"
15 | }
16 | }
17 |
18 | dependencies {
19 | api project(":customers-common")
20 |
21 | api "io.eventuate.local.java:eventuate-client-java-micronaut"
22 | api "io.eventuate.local.java:eventuate-local-java-micronaut-jdbc"
23 | api "io.eventuate.common:eventuate-common-micronaut-data-jdbc:$eventuateCommonVersion"
24 |
25 | annotationProcessor "javax.persistence:javax.persistence-api:2.2"
26 | api 'io.micronaut.sql:micronaut-jdbc-hikari'
27 | annotationProcessor "io.micronaut:micronaut-inject-java"
28 | annotationProcessor "io.micronaut:micronaut-validation"
29 | api "io.micronaut:micronaut-inject"
30 | api "io.micronaut:micronaut-validation"
31 | api "io.micronaut:micronaut-runtime"
32 | api "io.micronaut:micronaut-http-server-netty"
33 | api 'io.micronaut:micronaut-management'
34 | runtimeOnly "ch.qos.logback:logback-classic:1.2.3"
35 | annotationProcessor "io.micronaut.configuration:micronaut-openapi"
36 | api "io.swagger.core.v3:swagger-annotations"
37 | api "io.eventuate.util:eventuate-util-swagger-ui:$eventuateUtilVersion"
38 |
39 | testImplementation 'com.jayway.restassured:rest-assured:2.9.0'
40 | testImplementation 'org.mockito:mockito-all:1.10.19'
41 | testImplementation 'com.pholser:junit-quickcheck-core:0.7'
42 | testImplementation 'com.pholser:junit-quickcheck-generators:0.7'
43 | testImplementation "io.eventuate.local.java:eventuate-client-java-micronaut-jdbc"
44 | testAnnotationProcessor "io.micronaut:micronaut-inject-java"
45 | testImplementation "org.junit.jupiter:junit-jupiter-api"
46 | testImplementation "io.micronaut.test:micronaut-test-junit5"
47 | testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
48 | }
49 |
50 | mainClassName = "net.chrisrichardson.eventstore.examples.customersandorders.customersservice.CustomersServiceMain"
51 |
52 | shadowJar {
53 | mergeServiceFiles()
54 | }
55 |
56 | // use JUnit 5 platform
57 | test {
58 | useJUnitPlatform()
59 | }
60 |
--------------------------------------------------------------------------------
/orders-history-view-service/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "io.spring.dependency-management"
3 | id "java"
4 | id "com.github.johnrengelman.shadow" version "4.0.2"
5 | id "application"
6 | }
7 |
8 | apply plugin: 'docker-compose'
9 |
10 | dockerCompose.isRequiredBy(test)
11 |
12 | dockerCompose {
13 | useComposeFiles = ['docker-compose-integration-tests.yml']
14 | }
15 |
16 | repositories {
17 | mavenCentral()
18 | }
19 |
20 | dependencyManagement {
21 | imports {
22 | mavenBom "io.micronaut:micronaut-bom:$micronautVersion"
23 | }
24 | }
25 |
26 | dependencies {
27 | api project(":orders-history-common")
28 |
29 | api "io.eventuate.local.java:eventuate-client-java-micronaut-events"
30 | api "io.eventuate.local.java:eventuate-local-java-micronaut-events"
31 |
32 | implementation("io.micronaut.mongodb:micronaut-mongo-sync")
33 | annotationProcessor "io.micronaut:micronaut-inject-java"
34 | annotationProcessor "io.micronaut:micronaut-validation"
35 | api "io.micronaut:micronaut-inject"
36 | api "io.micronaut:micronaut-validation"
37 | api "io.micronaut:micronaut-runtime"
38 | api "io.micronaut:micronaut-http-server-netty"
39 | api 'io.micronaut:micronaut-management'
40 | runtimeOnly "ch.qos.logback:logback-classic:1.2.3"
41 | annotationProcessor "io.micronaut.configuration:micronaut-openapi"
42 | api "io.swagger.core.v3:swagger-annotations"
43 | api "io.eventuate.util:eventuate-util-swagger-ui:$eventuateUtilVersion"
44 |
45 | testImplementation 'org.mockito:mockito-all:1.10.19'
46 | testImplementation project(":customers-common")
47 | testImplementation "junit:junit:4.12"
48 | testImplementation 'com.jayway.restassured:rest-assured:2.3.0'
49 | testImplementation "com.jayway.jsonpath:json-path:2.3.0"
50 | testAnnotationProcessor "io.micronaut:micronaut-inject-java"
51 | testImplementation "org.junit.jupiter:junit-jupiter-api"
52 | testImplementation "io.micronaut.test:micronaut-test-junit5"
53 | testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
54 | }
55 |
56 | mainClassName = "net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.OrderHistoryQuerySideService"
57 |
58 | shadowJar {
59 | mergeServiceFiles()
60 | }
61 |
62 | // use JUnit 5 platform
63 | test {
64 | useJUnitPlatform()
65 | }
66 |
--------------------------------------------------------------------------------
/customers-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/customersservice/backend/Customer.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.customersservice.backend;
2 |
3 | import io.eventuate.Event;
4 | import io.eventuate.EventUtil;
5 | import io.eventuate.ReflectiveMutableCommandProcessingAggregate;
6 | import net.chrisrichardson.eventstore.examples.customersandorders.common.customer.CustomerCreatedEvent;
7 | import net.chrisrichardson.eventstore.examples.customersandorders.common.customer.CustomerCreditLimitExceededEvent;
8 | import net.chrisrichardson.eventstore.examples.customersandorders.common.customer.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 |
--------------------------------------------------------------------------------
/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.common.order.OrderApprovedEvent;
8 | import net.chrisrichardson.eventstore.examples.customersandorders.common.order.OrderCreatedEvent;
9 | import net.chrisrichardson.eventstore.examples.customersandorders.common.order.OrderRejectedEvent;
10 | import net.chrisrichardson.eventstore.examples.customersandorders.common.order.OrderState;
11 | import org.junit.Test;
12 |
13 | import java.util.List;
14 |
15 | import static org.junit.Assert.assertEquals;
16 |
17 | public class OrderTest {
18 |
19 | Money orderTotal = new Money(10);
20 | String customerId = "customerID";
21 |
22 | @Test
23 | public void testCreate() {
24 | Order order = new Order();
25 |
26 | List events = order.process(new CreateOrderCommand(customerId, orderTotal));
27 |
28 | assertEquals(EventUtil.events(new OrderCreatedEvent(customerId, orderTotal)), events);
29 |
30 | order.apply((OrderCreatedEvent) events.get(0));
31 |
32 | assertEquals(OrderState.CREATED, order.getState());
33 |
34 | }
35 |
36 | @Test
37 | public void testApprove() {
38 | Order order = new Order();
39 |
40 | order.apply((OrderCreatedEvent) order.process(new CreateOrderCommand(customerId, orderTotal)).get(0));
41 |
42 | List events = order.process(new ApproveOrderCommand());
43 |
44 | assertEquals(EventUtil.events(new OrderApprovedEvent(customerId)), events);
45 |
46 | order.apply((OrderApprovedEvent) events.get(0));
47 |
48 | assertEquals(OrderState.APPROVED, order.getState());
49 | }
50 |
51 | @Test
52 | public void testReject() {
53 | Order order = new Order();
54 |
55 | order.apply((OrderCreatedEvent) order.process(new CreateOrderCommand(customerId, orderTotal)).get(0));
56 |
57 | List events = order.process(new RejectOrderCommand());
58 |
59 | assertEquals(EventUtil.events(new OrderRejectedEvent(customerId)), events);
60 |
61 | order.apply((OrderRejectedEvent) events.get(0));
62 |
63 | assertEquals(OrderState.REJECTED, order.getState());
64 |
65 | }
66 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/backend/AbstractRepository.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.backend;
2 |
3 | import com.mongodb.BasicDBObject;
4 | import com.mongodb.MongoCommandException;
5 | import com.mongodb.client.MongoClient;
6 | import com.mongodb.client.MongoCollection;
7 | import com.mongodb.client.model.FindOneAndUpdateOptions;
8 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money;
9 | import org.bson.Document;
10 | import org.bson.types.Decimal128;
11 |
12 | import java.math.BigDecimal;
13 | import java.util.Optional;
14 |
15 | public class AbstractRepository {
16 | private static final String DATABASE = "customers_and_orders";
17 | private static final int REPEAT_ON_FAILURE_COUNT = 10;
18 | private final String collection;
19 |
20 | private MongoClient mongoClient;
21 |
22 | public AbstractRepository(MongoClient mongoClient, String collection) {
23 | this.mongoClient = mongoClient;
24 | this.collection = collection;
25 | }
26 |
27 | protected void findOneAndUpdate(String id, BasicDBObject newValue) {
28 | collection().findOneAndUpdate(new BasicDBObject("_id", id),
29 | new BasicDBObject("$set", newValue),
30 | upsertOptions());
31 | }
32 |
33 | protected Optional findOne(String id) {
34 | Document customerDocument = collection()
35 | .find(new BasicDBObject("_id", id))
36 | .first();
37 |
38 | return Optional
39 | .ofNullable(customerDocument);
40 | }
41 |
42 | protected FindOneAndUpdateOptions upsertOptions() {
43 | FindOneAndUpdateOptions opts = new FindOneAndUpdateOptions();
44 |
45 | opts.upsert(true);
46 |
47 | return opts;
48 | }
49 |
50 | protected MongoCollection collection() {
51 | return mongoClient
52 | .getDatabase(DATABASE)
53 | .getCollection(collection);
54 | }
55 |
56 | protected Money getMoney(Document document, String field) {
57 | BigDecimal bigDecimal = Optional
58 | .ofNullable(document.get(field, Decimal128.class))
59 | .map(Decimal128::bigDecimalValue)
60 | .orElse(new BigDecimal(0));
61 |
62 | return new Money(bigDecimal);
63 | }
64 |
65 | protected void repeatOnFailure(Runnable action) {
66 | MongoCommandException mongoCommandException = null;
67 |
68 | for (int i = 0; i < REPEAT_ON_FAILURE_COUNT; i++) {
69 | try {
70 | action.run();
71 | return;
72 | } catch (MongoCommandException e) {
73 | mongoCommandException = e;
74 | }
75 | }
76 |
77 | throw new RuntimeException(mongoCommandException);
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/backend/OrderHistoryViewWorkflow.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.backend;
2 |
3 | import io.eventuate.DispatchedEvent;
4 | import io.eventuate.EventHandlerMethod;
5 | import io.eventuate.EventSubscriber;
6 | import io.eventuate.Subscriber;
7 | import net.chrisrichardson.eventstore.examples.customersandorders.common.customer.CustomerCreatedEvent;
8 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money;
9 | import net.chrisrichardson.eventstore.examples.customersandorders.common.order.OrderApprovedEvent;
10 | import net.chrisrichardson.eventstore.examples.customersandorders.common.order.OrderCreatedEvent;
11 | import net.chrisrichardson.eventstore.examples.customersandorders.common.order.OrderRejectedEvent;
12 | import org.slf4j.Logger;
13 | import org.slf4j.LoggerFactory;
14 |
15 | @EventSubscriber(id = "orderHistoryWorkflow")
16 | public class OrderHistoryViewWorkflow implements Subscriber {
17 | private Logger logger = LoggerFactory.getLogger(getClass());
18 |
19 | private OrderHistoryViewService orderHistoryViewService;
20 |
21 | public OrderHistoryViewWorkflow(OrderHistoryViewService orderHistoryViewService) {
22 | this.orderHistoryViewService = orderHistoryViewService;
23 | }
24 |
25 | @EventHandlerMethod
26 | public void createCustomer(DispatchedEvent de) {
27 | System.out.println(String.format("createCustomer invoked %s", de));
28 |
29 | String customerId = de.getEntityId();
30 | orderHistoryViewService.createCustomer(customerId, de.getEvent().getName(),
31 | de.getEvent().getCreditLimit());
32 | }
33 |
34 | @EventHandlerMethod
35 | public void createOrder(DispatchedEvent de) {
36 | System.out.println(String.format("createOrder invoked %s", de));
37 |
38 | String customerId = de.getEvent().getCustomerId();
39 | String orderId = de.getEntityId();
40 | Money orderTotal = de.getEvent().getOrderTotal();
41 | orderHistoryViewService.addOrder(customerId, orderId, orderTotal);
42 | }
43 |
44 | @EventHandlerMethod
45 | public void orderApproved(DispatchedEvent de) {
46 | System.out.println(String.format("orderApproved invoked %s", de));
47 |
48 | String customerId = de.getEvent().getCustomerId();
49 | String orderId = de.getEntityId();
50 | orderHistoryViewService.approveOrder(customerId, orderId); }
51 |
52 | @EventHandlerMethod
53 | public void orderRejected(DispatchedEvent de) {
54 | System.out.println(String.format("orderRejected invoked %s", de));
55 |
56 | String customerId = de.getEvent().getCustomerId();
57 | String orderId = de.getEntityId();
58 | orderHistoryViewService.rejectOrder(customerId, orderId);
59 | }
60 |
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/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.common.customer.CustomerCreatedEvent;
7 | import net.chrisrichardson.eventstore.examples.customersandorders.common.customer.CustomerCreditLimitExceededEvent;
8 | import net.chrisrichardson.eventstore.examples.customersandorders.common.customer.CustomerCreditReservedEvent;
9 | import org.junit.jupiter.api.BeforeEach;
10 | import org.junit.jupiter.api.Test;
11 |
12 | import java.util.List;
13 |
14 | import static io.eventuate.EventUtil.events;
15 | import static org.junit.Assert.assertEquals;
16 |
17 | public class CustomerTest {
18 |
19 | private Customer customer;
20 | private List events;
21 |
22 | @BeforeEach
23 | public void createEmptyCustomer() {
24 | customer = new Customer();
25 | }
26 |
27 | @Test
28 | public void testCreate() {
29 |
30 | process(new CreateCustomerCommand(CustomerMother.name, CustomerMother.creditLimit));
31 |
32 | assertEventEquals(new CustomerCreatedEvent(CustomerMother.name, CustomerMother.creditLimit));
33 |
34 | applyEventsToMutableAggregate();
35 |
36 | assertEquals(CustomerMother.creditLimit, customer.getCreditLimit());
37 | assertEquals(CustomerMother.creditLimit, customer.availableCredit());
38 | }
39 |
40 | private void applyEventsToMutableAggregate() {
41 | Aggregates.applyEventsToMutableAggregate(customer, events, DefaultMissingApplyEventMethodStrategy.INSTANCE);
42 | }
43 |
44 |
45 | @Test
46 | public void testReserveCredit() {
47 | initializeCustomer();
48 |
49 | process(new ReserveCreditCommand(CustomerMother.orderTotalWithinCreditLimit, CustomerMother.orderId));
50 |
51 | assertEventEquals(new CustomerCreditReservedEvent(CustomerMother.orderId, CustomerMother.orderTotalWithinCreditLimit));
52 |
53 | applyEventsToMutableAggregate();
54 |
55 | assertEquals(CustomerMother.creditLimit, customer.getCreditLimit());
56 | assertEquals(CustomerMother.creditLimit.subtract(CustomerMother.orderTotalWithinCreditLimit), customer.availableCredit());
57 |
58 | }
59 |
60 |
61 | @Test
62 | public void testCreditLimitExceeded() {
63 | initializeCustomer();
64 |
65 | process(new ReserveCreditCommand(CustomerMother.orderTotalThatExceedsCreditLimit, CustomerMother.orderId));
66 |
67 | assertEventEquals(new CustomerCreditLimitExceededEvent(CustomerMother.orderId));
68 |
69 | applyEventsToMutableAggregate();
70 |
71 | assertEquals(CustomerMother.creditLimit, customer.getCreditLimit());
72 | assertEquals(CustomerMother.creditLimit, customer.availableCredit());
73 | }
74 |
75 | private void process(T command) {
76 | events = customer.processCommand(command);
77 | }
78 |
79 |
80 | private void assertEventEquals(Event expectedEvent) {
81 | assertEquals(events(expectedEvent), events);
82 | }
83 |
84 | private void initializeCustomer() {
85 | process(new CreateCustomerCommand(CustomerMother.name, CustomerMother.creditLimit));
86 | applyEventsToMutableAggregate();
87 | }
88 |
89 | }
--------------------------------------------------------------------------------
/orders-history-view-service/src/main/java/net/chrisrichardson/eventstore/examples/customersandorders/ordershistoryviewservice/backend/CustomerViewRepository.java:
--------------------------------------------------------------------------------
1 | package net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.backend;
2 |
3 | import com.mongodb.BasicDBObject;
4 | import com.mongodb.client.MongoClient;
5 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money;
6 | import net.chrisrichardson.eventstore.examples.customersandorders.common.order.OrderState;
7 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistorycommon.CustomerView;
8 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistorycommon.OrderInfo;
9 | import org.bson.Document;
10 | import java.util.Optional;
11 |
12 | public class CustomerViewRepository extends AbstractRepository {
13 |
14 | public CustomerViewRepository(MongoClient mongoClient) {
15 | super(mongoClient, "customers");
16 | }
17 |
18 | public Optional findById(String customerId) {
19 | return findOne(customerId)
20 | .map(customerDocument -> {
21 | CustomerView customerView = new CustomerView();
22 |
23 | customerView.setId(customerId);
24 | customerView.setName(customerDocument.getString("name"));
25 | customerView.setCreditLimit(getMoney(customerDocument, "creditLimit"));
26 |
27 | Document orders = customerDocument.get("orders", Document.class);
28 |
29 | if (orders != null) {
30 | orders.forEach((key, value) -> {
31 | String orderId = key;
32 |
33 | Document orderDocument = (Document) value;
34 |
35 | OrderInfo orderInfo = new OrderInfo(orderId, getMoney(orderDocument, "orderTotal"));
36 |
37 | Optional
38 | .ofNullable(orderDocument.getString("state"))
39 | .map(OrderState::valueOf)
40 | .ifPresent(orderInfo::setState);
41 |
42 | customerView.getOrders().put(orderId, orderInfo);
43 | });
44 | }
45 |
46 | return customerView;
47 | });
48 | }
49 |
50 | public void addCustomer(String customerId, String customerName, Money creditLimit) {
51 | repeatOnFailure(() -> {
52 | BasicDBObject customer = new BasicDBObject()
53 | .append("name", customerName)
54 | .append("creditLimit", creditLimit.getAmount());
55 |
56 | findOneAndUpdate(customerId, customer);
57 | });
58 | }
59 |
60 | public void addOrder(String customerId, String orderId, Money orderTotal) {
61 | repeatOnFailure(() -> {
62 | BasicDBObject orderInfo = new BasicDBObject()
63 | .append("orderId", orderId)
64 | .append("orderTotal", orderTotal.getAmount());
65 |
66 | findOneAndUpdate(customerId, new BasicDBObject("orders." + orderId, orderInfo));
67 | });
68 | }
69 |
70 | public void updateOrderState(String customerId, String orderId, OrderState state) {
71 | repeatOnFailure(() -> {
72 | collection().findOneAndUpdate(new BasicDBObject("_id", customerId),
73 | new BasicDBObject("$set", new BasicDBObject(String.format("orders.%s.state", orderId), state.name())),
74 | upsertOptions());
75 | });
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/orders-service/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "io.spring.dependency-management"
3 | id "java"
4 | id "com.github.johnrengelman.shadow" version "4.0.2"
5 | id "application"
6 | }
7 |
8 | apply plugin: 'docker-compose'
9 |
10 | dockerCompose.isRequiredBy(test)
11 |
12 | dockerCompose {
13 | environment.put "EVENTUATE_COMMON_VERSION", eventuateCommonImageVersion
14 | environment.put "EVENTUATE_MESSAGING_KAFKA_IMAGE_VERSION", eventuateMessagingKafkaImageVersion
15 | environment.put "EVENTUATE_CDC_VERSION", eventuateCdcImageVersion
16 | environment.put "EVENTUATE_JAVA_BASE_IMAGE_VERSION", eventuateExamplesBaseImageVersion
17 |
18 | if ("EventuatePolling".equals(System.getenv("SPRING_PROFILES_ACTIVE")))
19 | useComposeFiles = ['../docker-compose-eventuate-local-postgres-polling.yml']
20 | else if ("PostgresWal".equals(System.getenv("SPRING_PROFILES_ACTIVE")))
21 | useComposeFiles = ['../docker-compose-eventuate-local-postgres-wal.yml']
22 | else
23 | useComposeFiles = ['../docker-compose-eventuate-local-mysql.yml']
24 | startedServices=["customercommandside"]
25 | projectName = null
26 |
27 | }
28 |
29 | repositories {
30 | mavenCentral()
31 | }
32 |
33 | dependencyManagement {
34 | imports {
35 | mavenBom "io.micronaut:micronaut-bom:$micronautVersion"
36 | }
37 | }
38 |
39 | dependencies {
40 | api project(":orders-common")
41 | api "org.springframework.boot:spring-boot-starter-web:$springBootVersion"
42 |
43 | api "io.eventuate.local.java:eventuate-client-java-micronaut"
44 | api "io.eventuate.local.java:eventuate-local-java-micronaut-jdbc"
45 | api "io.eventuate.common:eventuate-common-micronaut-data-jdbc:$eventuateCommonVersion"
46 |
47 | annotationProcessor "javax.persistence:javax.persistence-api:2.2"
48 | api 'io.micronaut.sql:micronaut-jdbc-hikari'
49 | annotationProcessor "io.micronaut:micronaut-inject-java"
50 | annotationProcessor "io.micronaut:micronaut-validation"
51 | api "io.micronaut:micronaut-inject"
52 | api "io.micronaut:micronaut-validation"
53 | api "io.micronaut:micronaut-runtime"
54 | api "io.micronaut:micronaut-http-server-netty"
55 | api 'io.micronaut:micronaut-management'
56 | runtimeOnly "ch.qos.logback:logback-classic:1.2.3"
57 | annotationProcessor "io.micronaut.configuration:micronaut-openapi"
58 | api "io.swagger.core.v3:swagger-annotations"
59 | api "io.eventuate.util:eventuate-util-swagger-ui:$eventuateUtilVersion"
60 |
61 | testImplementation 'org.mockito:mockito-all:1.10.19'
62 | testImplementation "io.eventuate.local.java:eventuate-client-java-micronaut-jdbc"
63 | testImplementation project(":customers-common")
64 | testImplementation "junit:junit:4.12"
65 | testImplementation 'com.jayway.restassured:rest-assured:2.3.0'
66 | testImplementation "com.jayway.jsonpath:json-path:2.3.0"
67 | testAnnotationProcessor "io.micronaut:micronaut-inject-java"
68 | testImplementation "org.junit.jupiter:junit-jupiter-api"
69 | testImplementation "io.micronaut.test:micronaut-test-junit5"
70 | testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
71 | }
72 |
73 | mainClassName = "net.chrisrichardson.eventstore.examples.customersandorders.ordersservice.OrdersServiceMain"
74 |
75 | shadowJar {
76 | mergeServiceFiles()
77 | }
78 |
79 | // use JUnit 5 platform
80 | test {
81 | useJUnitPlatform()
82 | }
83 |
--------------------------------------------------------------------------------
/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 io.micronaut.test.annotation.MicronautTest;
4 | import net.chrisrichardson.eventstore.examples.customersandorders.common.domain.Money;
5 | import net.chrisrichardson.eventstore.examples.customersandorders.common.order.OrderState;
6 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistorycommon.CustomerView;
7 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistorycommon.OrderView;
8 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.backend.CustomerViewRepository;
9 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.backend.OrderHistoryViewService;
10 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistoryviewservice.backend.OrderViewRepository;
11 | import org.junit.jupiter.api.Test;
12 |
13 | import javax.inject.Inject;
14 | import java.util.UUID;
15 |
16 | import static org.junit.Assert.assertEquals;
17 | import static org.junit.Assert.assertNotNull;
18 |
19 | @MicronautTest
20 | public class OrderHistoryViewServiceTest {
21 |
22 | @Inject
23 | private OrderHistoryViewService orderHistoryViewService;
24 |
25 | @Inject
26 | private CustomerViewRepository customerViewRepository;
27 |
28 | @Inject
29 | private OrderViewRepository orderViewRepository;
30 |
31 | @Test
32 | public void shouldCreateCustomerAndOrdersEtc() {
33 | String customerId = UUID.randomUUID().toString();
34 | Money creditLimit = new Money(2000);
35 | String customerName = "Fred";
36 |
37 | String orderId1 = UUID.randomUUID().toString();
38 | Money orderTotal1 = new Money(1234);
39 | String orderId2 = UUID.randomUUID().toString();
40 | Money orderTotal2 = new Money(3000);
41 |
42 | orderHistoryViewService.createCustomer(customerId, customerName, creditLimit);
43 | orderHistoryViewService.addOrder(customerId, orderId1, orderTotal1);
44 | orderHistoryViewService.approveOrder(customerId, orderId1);
45 |
46 | orderHistoryViewService.addOrder(customerId, orderId2, orderTotal2);
47 | orderHistoryViewService.rejectOrder(customerId, orderId2);
48 |
49 | CustomerView customerView = customerViewRepository.findById(customerId).get();
50 |
51 |
52 | assertEquals(2, customerView.getOrders().size());
53 | assertNotNull(customerView.getOrders().get(orderId1));
54 | assertNotNull(customerView.getOrders().get(orderId2));
55 | assertEquals(orderTotal1, customerView.getOrders().get(orderId1).getOrderTotal());
56 | assertEquals(OrderState.APPROVED, customerView.getOrders().get(orderId1).getState());
57 |
58 | assertNotNull(customerView.getOrders().get(orderId2));
59 | assertEquals(orderTotal2, customerView.getOrders().get(orderId2).getOrderTotal());
60 | assertEquals(OrderState.REJECTED, customerView.getOrders().get(orderId2).getState());
61 |
62 | OrderView orderView1 = orderViewRepository.findById(orderId1).get();
63 | assertEquals(orderTotal1, orderView1.getOrderTotal());
64 | assertEquals(OrderState.APPROVED, orderView1.getState());
65 |
66 | OrderView orderView2 = orderViewRepository.findById(orderId2).get();
67 | assertEquals(orderTotal2, orderView2.getOrderTotal());
68 | assertEquals(OrderState.REJECTED, orderView2.getState());
69 | }
70 |
71 |
72 | }
--------------------------------------------------------------------------------
/docker-compose-eventuate-local-postgres-wal.yml:
--------------------------------------------------------------------------------
1 | services:
2 | zookeeper:
3 | image: eventuateio/eventuate-zookeeper:$EVENTUATE_COMMON_VERSION
4 | ports:
5 | - 2181:2181
6 | environment:
7 | ZOOKEEPER_CLIENT_PORT: 2181
8 | KAFKA_HEAP_OPTS: -Xmx64m
9 |
10 | kafka:
11 | image: "eventuateio/eventuate-kafka:$EVENTUATE_MESSAGING_KAFKA_IMAGE_VERSION"
12 | ports:
13 | - 9092:9092
14 | - 29092:29092
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 | cdcservice:
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_DRIVER_CLASS_NAME: org.postgresql.Driver
48 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092
49 | EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181
50 | SPRING_PROFILES_ACTIVE: PostgresWal
51 | EVENTUATELOCAL_CDC_READER_NAME: PostgresWalReader
52 | EVENTUATE_CDC_TYPE: "EventuateLocal"
53 | EVENTUATE_OUTBOX_ID: 1
54 | JAVA_OPTS: -Xmx64m
55 |
56 | mongodb:
57 | image: mongo:4.2.12
58 | hostname: mongodb
59 | ports:
60 | - "27017:27017"
61 |
62 | customercommandside:
63 | build:
64 | context: ./customers-service/
65 | args:
66 | baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION?}
67 | ports:
68 | - "8081:8080"
69 | depends_on:
70 | - postgres
71 | - kafka
72 | - zookeeper
73 | environment:
74 | DB_URL: jdbc:postgresql://postgres/eventuate
75 | DB_USERNAME: eventuate
76 | DB_PASSWORD: eventuate
77 | DB_DRIVERCLASSNAME: org.postgresql.Driver
78 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092
79 |
80 | orderhistoryqueryside:
81 | build:
82 | context: ./orders-history-view-service/
83 | args:
84 | baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION?}
85 | ports:
86 | - "8082:8080"
87 | depends_on:
88 | - mongodb
89 | - postgres
90 | - kafka
91 | - zookeeper
92 | environment:
93 | SPRING_DATA_MONGODB_URI: mongodb://mongodb/customers_and_orders
94 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092
95 |
96 | ordercommandside:
97 | build:
98 | context: ./orders-service/
99 | args:
100 | baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION?}
101 | ports:
102 | - "8083:8080"
103 | depends_on:
104 | - postgres
105 | - kafka
106 | - zookeeper
107 | - customercommandside
108 | environment:
109 | CUSTOMER_SERVICE_URL: http://customercommandside:8080/customers/{customerId}
110 | ENDPOINTS_SENSITIVE: "false"
111 | DB_URL: jdbc:postgresql://postgres/eventuate
112 | DB_USERNAME: eventuate
113 | DB_PASSWORD: eventuate
114 | DB_DRIVERCLASSNAME: org.postgresql.Driver
115 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092
116 |
--------------------------------------------------------------------------------
/docker-compose-eventuate-local-postgres-polling.yml:
--------------------------------------------------------------------------------
1 | services:
2 | zookeeper:
3 | image: eventuateio/eventuate-zookeeper:$EVENTUATE_COMMON_VERSION
4 | ports:
5 | - 2181:2181
6 | environment:
7 | ZOOKEEPER_CLIENT_PORT: 2181
8 | KAFKA_HEAP_OPTS: -Xmx64m
9 |
10 | kafka:
11 | image: "eventuateio/eventuate-kafka:$EVENTUATE_MESSAGING_KAFKA_IMAGE_VERSION"
12 | ports:
13 | - 9092:9092
14 | - 29092:29092
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 | cdcservice:
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_DRIVER_CLASS_NAME: org.postgresql.Driver
48 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092
49 | EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181
50 | SPRING_PROFILES_ACTIVE: EventuatePolling
51 | EVENTUATELOCAL_CDC_READER_NAME: PostgresPollingReader
52 | EVENTUATE_CDC_TYPE: "EventuateLocal"
53 | EVENTUATE_OUTBOX_ID: 1
54 | JAVA_OPTS: -Xmx64m
55 |
56 | mongodb:
57 | image: mongo:4.2.12
58 | hostname: mongodb
59 | ports:
60 | - "27017:27017"
61 |
62 | customercommandside:
63 | build:
64 | context: ./customers-service/
65 | args:
66 | baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION?}
67 | ports:
68 | - "8081:8080"
69 | depends_on:
70 | - postgres
71 | - kafka
72 | - zookeeper
73 | environment:
74 | DB_URL: jdbc:postgresql://postgres/eventuate
75 | DB_USERNAME: eventuate
76 | DB_PASSWORD: eventuate
77 | DB_DRIVERCLASSNAME: org.postgresql.Driver
78 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092
79 | SPRING_PROFILES_ACTIVE: EventuatePolling
80 | EVENTUATELOCAL_CDC_READER_NAME: PostgresPollingReader
81 |
82 | orderhistoryqueryside:
83 | build:
84 | context: ./orders-history-view-service/
85 | args:
86 | baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION?}
87 | ports:
88 | - "8082:8080"
89 | depends_on:
90 | - mongodb
91 | - postgres
92 | - kafka
93 | - zookeeper
94 | environment:
95 | SPRING_DATA_MONGODB_URI: mongodb://mongodb/customers_and_orders
96 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092
97 |
98 | ordercommandside:
99 | build:
100 | context: ./orders-service/
101 | args:
102 | baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION?}
103 | ports:
104 | - "8083:8080"
105 | depends_on:
106 | - postgres
107 | - kafka
108 | - zookeeper
109 | - customercommandside
110 | environment:
111 | CUSTOMER_SERVICE_URL: http://customercommandside:8080/customers/{customerId}
112 | ENDPOINTS_SENSITIVE: "false"
113 | DB_URL: jdbc:postgresql://postgres/eventuate
114 | DB_USERNAME: eventuate
115 | DB_PASSWORD: eventuate
116 | DB_DRIVERCLASSNAME: org.postgresql.Driver
117 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092
118 |
--------------------------------------------------------------------------------
/docker-compose-eventuate-local-mysql.yml:
--------------------------------------------------------------------------------
1 | services:
2 | zookeeper:
3 | image: eventuateio/eventuate-zookeeper:$EVENTUATE_COMMON_VERSION
4 | ports:
5 | - 2181:2181
6 | environment:
7 | ZOOKEEPER_CLIENT_PORT: 2181
8 | KAFKA_HEAP_OPTS: -Xmx64m
9 |
10 | kafka:
11 | image: "eventuateio/eventuate-kafka:$EVENTUATE_MESSAGING_KAFKA_IMAGE_VERSION"
12 | ports:
13 | - 9092:9092
14 | - 29092:29092
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 | cdcservice:
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_MYSQL_BINLOG_CLIENT_UNIQUE_ID: 1234567890
54 | EVENTUATELOCAL_CDC_READ_OLD_DEBEZIUM_DB_OFFSET_STORAGE_TOPIC: "false"
55 | EVENTUATE_CDC_TYPE: "EventuateLocal"
56 | EVENTUATE_OUTBOX_ID: 1
57 | JAVA_OPTS: -Xmx64m
58 |
59 | mongodb:
60 | image: mongo:4.2.12
61 | hostname: mongodb
62 | ports:
63 | - "27017:27017"
64 |
65 | customercommandside:
66 | build:
67 | context: ./customers-service/
68 | args:
69 | baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION}
70 | ports:
71 | - "8081:8080"
72 | depends_on:
73 | - mysql
74 | - kafka
75 | - zookeeper
76 | environment:
77 | DB_URL: jdbc:mysql://mysql/eventuate
78 | DB_DRIVERCLASSNAME: com.mysql.jdbc.Driver
79 | DB_USERNAME: mysqluser
80 | DB_PASSWORD: mysqlpw
81 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092
82 |
83 | orderhistoryqueryside:
84 | build:
85 | context: ./orders-history-view-service/
86 | args:
87 | baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION}
88 | ports:
89 | - "8082:8080"
90 | depends_on:
91 | - mongodb
92 | - mysql
93 | - kafka
94 | - zookeeper
95 | environment:
96 | SPRING_DATA_MONGODB_URI: mongodb://mongodb/customers_and_orders
97 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092
98 |
99 | ordercommandside:
100 | build:
101 | context: ./orders-service/
102 | args:
103 | baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION}
104 | ports:
105 | - "8083:8080"
106 | depends_on:
107 | - mysql
108 | - kafka
109 | - zookeeper
110 | - customercommandside
111 | environment:
112 | CUSTOMER_SERVICE_URL: http://customercommandside:8080/customers/{customerId}
113 | ENDPOINTS_SENSITIVE: "false"
114 | DB_URL: jdbc:mysql://mysql/eventuate
115 | DB_DRIVERCLASSNAME: com.mysql.jdbc.Driver
116 | DB_USERNAME: mysqluser
117 | DB_PASSWORD: mysqlpw
118 | EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092
119 |
--------------------------------------------------------------------------------
/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.order.OrderState;
5 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistorycommon.CustomerView;
6 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistorycommon.OrderInfo;
7 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistorycommon.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 io.eventuate.util.test.async.Eventually.eventuallyReturning;
16 | import static org.junit.Assert.assertEquals;
17 | import static org.junit.Assert.assertNotNull;
18 | import static org.junit.Assert.fail;
19 |
20 | public abstract class AbstractCustomerAndOrdersIntegrationTest {
21 | protected Logger logger = LoggerFactory.getLogger(getClass());
22 |
23 | private final Money creditLimit = new Money(1000);
24 |
25 | protected class IntegrationTestCustomerNotFoundException extends RuntimeException {
26 | public IntegrationTestCustomerNotFoundException(Throwable cause) {
27 | super(cause);
28 | }
29 | }
30 |
31 | @Test
32 | public void shouldCreateAndApproveOrder() throws Exception {
33 |
34 | String customerId = createCustomer(creditLimit);
35 |
36 | Thread.sleep(4000);
37 |
38 | Money orderTotal = new Money(720);
39 |
40 | String orderId = createOrder(customerId, orderTotal);
41 |
42 | eventually(40, 500, TimeUnit.MILLISECONDS, () -> {
43 | OrderView o = getOrderView(orderId);
44 | assertNotNull(o);
45 | assertEquals(OrderState.APPROVED, o.getState());
46 | });
47 |
48 | CustomerView customerView = eventuallyReturning(() -> {
49 | CustomerView cv = getCustomerView(customerId);
50 | assertNotNull(cv);
51 | OrderInfo orderInfo = cv.getOrders().get(orderId);
52 | assertNotNull(orderInfo);
53 | assertEquals(OrderState.APPROVED, orderInfo.getState());
54 | return cv;
55 | });
56 |
57 | assertEquals(creditLimit, customerView.getCreditLimit());
58 | assertEquals(orderTotal, customerView.getOrders().get(orderId).getOrderTotal());
59 |
60 | }
61 |
62 | @Test
63 | public void shouldCreateAndRejectOrder() {
64 |
65 | String customerId = createCustomer(creditLimit);
66 |
67 | Money orderTotal = creditLimit.add(new Money(1));
68 |
69 | String orderId = createOrder(customerId, orderTotal);
70 |
71 | eventually(40, 500, TimeUnit.MILLISECONDS, () -> {
72 | OrderView o = getOrderView(orderId);
73 | assertNotNull(o);
74 | assertEquals(OrderState.REJECTED, o.getState());
75 | });
76 |
77 | CustomerView customerView = eventuallyReturning(() -> {
78 | CustomerView cv = getCustomerView(customerId);
79 | assertNotNull(cv);
80 | OrderInfo orderInfo = cv.getOrders().get(orderId);
81 | assertNotNull(orderInfo);
82 | assertEquals(OrderState.REJECTED, orderInfo.getState());
83 | return cv;
84 | });
85 |
86 | assertEquals(creditLimit, customerView.getCreditLimit());
87 | assertEquals(orderTotal, customerView.getOrders().get(orderId).getOrderTotal());
88 |
89 | }
90 |
91 | @Test
92 | public void shouldRejectOrderWithInvalidCustomerId() {
93 |
94 | Money orderTotal = new Money(720);
95 |
96 | try {
97 | createOrder("unknown-customer-id", orderTotal);
98 | fail();
99 | } catch (IntegrationTestCustomerNotFoundException e) {
100 | // Expected
101 | }
102 | }
103 |
104 | protected abstract CustomerView getCustomerView(String customerId);
105 |
106 | protected abstract OrderView getOrderView(String orderId);
107 |
108 | protected abstract String createOrder(String customerId, Money orderTotal);
109 |
110 | protected abstract String createCustomer(Money creditLimit);
111 | }
112 |
--------------------------------------------------------------------------------
/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.customerscommon.CreateCustomerRequest;
6 | import net.chrisrichardson.eventstore.examples.customersandorders.customerscommon.CreateCustomerResponse;
7 | import net.chrisrichardson.eventstore.examples.customersandorders.orderscommmon.CreateOrderRequest;
8 | import net.chrisrichardson.eventstore.examples.customersandorders.orderscommmon.CreateOrderResponse;
9 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistorycommon.CustomerView;
10 | import net.chrisrichardson.eventstore.examples.customersandorders.ordershistorycommon.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 |
22 | import static org.junit.Assert.assertEquals;
23 |
24 | @RunWith(SpringJUnit4ClassRunner.class)
25 | @SpringBootTest(classes = CustomersAndOrdersE2ETestConfiguration.class, webEnvironment = SpringBootTest.WebEnvironment.NONE)
26 | public class CustomersAndOrdersE2ETest extends AbstractCustomerAndOrdersIntegrationTest {
27 |
28 | @Value("#{systemEnvironment['DOCKER_HOST_IP']}")
29 | private String hostName;
30 |
31 | private String baseUrlCustomers(String path) {
32 | return "http://"+hostName+":8081/" + path;
33 | }
34 |
35 | private String baseUrlOrders(String path) {
36 | return "http://"+hostName+":8083/" + path;
37 | }
38 |
39 | private String baseUrlOrderHistory(String path) {
40 | return "http://"+hostName+":8082/" + path;
41 | }
42 |
43 |
44 | @Autowired
45 | RestTemplate restTemplate;
46 |
47 | private CustomerView getCustomer(String customerId) {
48 | try {
49 | ResponseEntity getCustomer =
50 | restTemplate.getForEntity(baseUrlOrderHistory("customers/" + customerId), CustomerView.class);
51 | assertEquals(HttpStatus.OK, getCustomer.getStatusCode());
52 | return getCustomer.getBody();
53 | } catch (HttpClientErrorException e) {
54 | if (e.getStatusCode() == HttpStatus.NOT_FOUND)
55 | throw new RuntimeException("Cannot find customer "+ customerId, e);
56 | else
57 | throw e;
58 | }
59 | }
60 |
61 |
62 | @Override
63 | protected CustomerView getCustomerView(String customerId) {
64 | return getCustomer(customerId);
65 | }
66 |
67 | @Override
68 | protected OrderView getOrderView(String orderId) {
69 | try {
70 | ResponseEntity getCustomer =
71 | restTemplate.getForEntity(baseUrlOrderHistory("orders/" + orderId), OrderView.class);
72 | assertEquals(HttpStatus.OK, getCustomer.getStatusCode());
73 | return getCustomer.getBody();
74 | } catch (HttpClientErrorException e) {
75 | if (e.getStatusCode() == HttpStatus.NOT_FOUND)
76 | throw new RuntimeException("Cannot find order "+ orderId, e);
77 | else
78 | throw e;
79 | }
80 | }
81 |
82 | @Override
83 | protected String createOrder(String customerId, Money orderTotal) {
84 | try {
85 | ResponseEntity orderResponse =
86 | restTemplate.postForEntity(baseUrlOrders("orders"), new CreateOrderRequest(customerId, orderTotal), CreateOrderResponse.class);
87 | assertEquals(HttpStatus.OK, orderResponse.getStatusCode());
88 | return orderResponse.getBody().getOrderId();
89 | } catch (HttpClientErrorException e) {
90 | switch (e.getStatusCode()) {
91 | case BAD_REQUEST:
92 | throw new IntegrationTestCustomerNotFoundException(e);
93 | default:
94 | throw e;
95 | }
96 | }
97 | }
98 |
99 | @Override
100 | protected String createCustomer(Money creditLimit) {
101 | ResponseEntity customerResponse =
102 | restTemplate.postForEntity(baseUrlCustomers("customers"), new CreateCustomerRequest("Fred", creditLimit), CreateCustomerResponse.class);
103 |
104 | assertEquals(HttpStatus.OK, customerResponse.getStatusCode());
105 | CreateCustomerResponse r = customerResponse.getBody();
106 | return r.getCustomerId();
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------