From 55d70ea5ecd3d3e87d37c8b63ac47890fdbb8c1e Mon Sep 17 00:00:00 2001 From: Artem Sidorkin Date: Wed, 28 Apr 2021 19:37:42 +0300 Subject: [PATCH 1/2] Added reactive version of customer service. --- .github/workflows/build.yml | 46 +++++ .github/workflows/dump-messages.sh | 6 + .github/workflows/print-container-logs.sh | 20 ++ _build-and-test-all.sh | 36 ++++ build-and-test-all-mysql-binlog.sh | 9 + build-and-test-everything.sh | 16 ++ build.gradle | 83 +++++++++ buildSrc/src/main/groovy/ServicePlugin.groovy | 29 +++ common-swagger/build.gradle | 3 + .../CommonSwaggerConfiguration.java | 13 ++ common/build.gradle | 4 + .../common/domain/Money.java | 61 +++++++ customer-service-api-messaging/build.gradle | 4 + .../events/AbstractCustomerOrderEvent.java | 20 ++ .../domain/events/CustomerCreatedEvent.java | 32 ++++ .../events/CustomerCreditReleasedEvent.java | 11 ++ .../CustomerCreditReservationFailedEvent.java | 13 ++ .../events/CustomerCreditReservedEvent.java | 11 ++ .../domain/events/CustomerEvent.java | 6 + .../domain/events/CustomerSnapshotEvent.java | 43 +++++ .../events/CustomerValidationFailedEvent.java | 11 ++ customer-service-api-web/build.gradle | 3 + .../webapi/CreateCustomerRequest.java | 26 +++ .../webapi/CreateCustomerResponse.java | 21 +++ customer-service/Dockerfile | 3 + customer-service/build.gradle | 23 +++ .../customers/CustomerConfiguration.java | 33 ++++ .../customers/CustomerServiceMain.java | 14 ++ .../customers/domain/Customer.java | 54 ++++++ .../CustomerCreditLimitExceededException.java | 4 + .../customers/domain/CustomerRepository.java | 6 + .../customers/domain/ReservedCredit.java | 60 ++++++ .../domain/ReservedCreditRepository.java | 10 + .../customers/service/CustomerService.java | 115 ++++++++++++ .../customers/web/CustomerController.java | 29 +++ .../web/CustomerWebConfiguration.java | 12 ++ .../customers/web/OrderEventHttpConsumer.java | 33 ++++ .../src/main/resources/application.properties | 19 ++ docker-compose-mysql-binlog.yml | 159 ++++++++++++++++ end-to-end-tests/build.gradle | 11 ++ .../CustomersAndOrdersEndToEndTest.java | 160 ++++++++++++++++ ...ersAndOrdersEndToEndTestConfiguration.java | 30 +++ .../src/test/resources/application.properties | 1 + gradle.properties | 25 +++ gradle/gradlew | 164 +++++++++++++++++ gradle/gradlew.bat | 90 +++++++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54783 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 172 ++++++++++++++++++ gradlew.bat | 84 +++++++++ mongodb-cli.sh | 3 + mssql-cli.sh | 9 + mysql-cli.sh | 7 + mysql/8.initialize-database.sql | 17 ++ mysql/Dockerfile | 3 + order-history-service-api-web/build.gradle | 9 + .../orderhistory/common/CustomerView.java | 48 +++++ .../orderhistory/common/OrderInfo.java | 37 ++++ .../orderhistory/common/OrderView.java | 34 ++++ order-service-api-messaging/build.gradle | 4 + order-service-api-messaging/pom.xml | 32 ++++ .../domain/events/OrderApprovedEvent.java | 17 ++ .../events/OrderCancelConfirmedEvent.java | 17 ++ .../domain/events/OrderCancelledEvent.java | 17 ++ .../domain/events/OrderCreatedEvent.java | 17 ++ .../orders/domain/events/OrderDetails.java | 26 +++ .../orders/domain/events/OrderEvent.java | 6 + .../domain/events/OrderRejectedEvent.java | 17 ++ .../domain/events/OrderSnapshotEvent.java | 53 ++++++ .../orders/domain/events/OrderState.java | 3 + order-service-api-web/build.gradle | 7 + order-service-api-web/pom.xml | 33 ++++ .../orders/webapi/CreateOrderRequest.java | 25 +++ .../orders/webapi/CreateOrderResponse.java | 17 ++ .../orders/webapi/GetOrderResponse.java | 33 ++++ postgres-cli.sh | 7 + settings.gradle | 14 ++ wait-for-mysql.sh | 7 + wait-for-services.sh | 34 ++++ 79 files changed, 2397 insertions(+) create mode 100644 .github/workflows/build.yml create mode 100644 .github/workflows/dump-messages.sh create mode 100644 .github/workflows/print-container-logs.sh create mode 100755 _build-and-test-all.sh create mode 100755 build-and-test-all-mysql-binlog.sh create mode 100755 build-and-test-everything.sh create mode 100644 build.gradle create mode 100644 buildSrc/src/main/groovy/ServicePlugin.groovy create mode 100755 common-swagger/build.gradle create mode 100755 common-swagger/src/main/java/io/eventuate/examples/tram/ordersandcustomers/commonswagger/CommonSwaggerConfiguration.java create mode 100644 common/build.gradle create mode 100644 common/src/main/java/io/eventuate/examples/tram/ordersandcustomers/common/domain/Money.java create mode 100644 customer-service-api-messaging/build.gradle create mode 100644 customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/AbstractCustomerOrderEvent.java create mode 100644 customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreatedEvent.java create mode 100644 customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReleasedEvent.java create mode 100644 customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReservationFailedEvent.java create mode 100644 customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReservedEvent.java create mode 100644 customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerEvent.java create mode 100644 customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerSnapshotEvent.java create mode 100644 customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerValidationFailedEvent.java create mode 100644 customer-service-api-web/build.gradle create mode 100644 customer-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/webapi/CreateCustomerRequest.java create mode 100644 customer-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/webapi/CreateCustomerResponse.java create mode 100755 customer-service/Dockerfile create mode 100644 customer-service/build.gradle create mode 100644 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/CustomerConfiguration.java create mode 100755 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/CustomerServiceMain.java create mode 100644 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/Customer.java create mode 100644 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/CustomerCreditLimitExceededException.java create mode 100644 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/CustomerRepository.java create mode 100644 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/ReservedCredit.java create mode 100644 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/ReservedCreditRepository.java create mode 100644 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/service/CustomerService.java create mode 100644 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/web/CustomerController.java create mode 100755 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/web/CustomerWebConfiguration.java create mode 100644 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/web/OrderEventHttpConsumer.java create mode 100644 customer-service/src/main/resources/application.properties create mode 100755 docker-compose-mysql-binlog.yml create mode 100644 end-to-end-tests/build.gradle create mode 100644 end-to-end-tests/src/test/java/io/eventuate/examples/tram/ordersandcustomers/endtoendtests/CustomersAndOrdersEndToEndTest.java create mode 100755 end-to-end-tests/src/test/java/io/eventuate/examples/tram/ordersandcustomers/endtoendtests/CustomersAndOrdersEndToEndTestConfiguration.java create mode 100755 end-to-end-tests/src/test/resources/application.properties create mode 100644 gradle.properties create mode 100755 gradle/gradlew create mode 100755 gradle/gradlew.bat create mode 100755 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100755 gradlew.bat create mode 100755 mongodb-cli.sh create mode 100755 mssql-cli.sh create mode 100755 mysql-cli.sh create mode 100644 mysql/8.initialize-database.sql create mode 100644 mysql/Dockerfile create mode 100644 order-history-service-api-web/build.gradle create mode 100644 order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/CustomerView.java create mode 100644 order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/OrderInfo.java create mode 100644 order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/OrderView.java create mode 100644 order-service-api-messaging/build.gradle create mode 100644 order-service-api-messaging/pom.xml create mode 100644 order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderApprovedEvent.java create mode 100644 order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCancelConfirmedEvent.java create mode 100644 order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCancelledEvent.java create mode 100644 order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCreatedEvent.java create mode 100644 order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderDetails.java create mode 100644 order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderEvent.java create mode 100644 order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderRejectedEvent.java create mode 100644 order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderSnapshotEvent.java create mode 100644 order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderState.java create mode 100644 order-service-api-web/build.gradle create mode 100644 order-service-api-web/pom.xml create mode 100644 order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/CreateOrderRequest.java create mode 100644 order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/CreateOrderResponse.java create mode 100644 order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/GetOrderResponse.java create mode 100755 postgres-cli.sh create mode 100644 settings.gradle create mode 100755 wait-for-mysql.sh create mode 100755 wait-for-services.sh diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..d1dd3ed --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,46 @@ +name: Build + +on: + push: + pull_request: + types: + - opened + - edited +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - name: Checkout source + uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: '11' # The JDK version to make available on the path. + java-package: jdk # (jre, jdk, or jdk+fx) - defaults to jdk + architecture: x64 # (x64 or x86) - defaults to x64 + + - name: Build + run: ./build-and-test-all-mysql-binlog.sh + + - name: Save test results + if: ${{ always() }} + uses: actions/upload-artifact@v2 + with: + name: test-reports + path: ./end-to-end-tests/build/reports + + - name: print messages + run: ./.github/workflows/dump-messages.sh + if: ${{ failure() }} + + - name: get container logs + run: ./.github/workflows/print-container-logs.sh + if: ${{ always() }} + + - name: Save container logs + if: ${{ always() }} + uses: actions/upload-artifact@v2 + with: + name: container-logs + path: ~/container-logs diff --git a/.github/workflows/dump-messages.sh b/.github/workflows/dump-messages.sh new file mode 100644 index 0000000..d56b826 --- /dev/null +++ b/.github/workflows/dump-messages.sh @@ -0,0 +1,6 @@ +#! /bin/bash + +set -e + +echo 'select * from eventuate.message\G' | ./mysql-cli.sh -i +echo 'select * from eventuate.received_messages\G' | ./mysql-cli.sh -i diff --git a/.github/workflows/print-container-logs.sh b/.github/workflows/print-container-logs.sh new file mode 100644 index 0000000..4f2d69d --- /dev/null +++ b/.github/workflows/print-container-logs.sh @@ -0,0 +1,20 @@ +#! /bin/bash -e + +CONTAINER_IDS=$(docker ps -a -q) + +for id in $CONTAINER_IDS ; do + echo "\n--------------------" + echo "logs of:\n" + docker ps -a -f "id=$id" + echo "\n" + docker logs $id + echo "--------------------\n" +done + +mkdir -p ~/container-logs + +docker ps -a > ~/container-logs/containers.txt + +for name in $(docker ps -a --format "{{.Names}}") ; do + docker logs $name > ~/container-logs/${name}.log +done diff --git a/_build-and-test-all.sh b/_build-and-test-all.sh new file mode 100755 index 0000000..e0c3d8b --- /dev/null +++ b/_build-and-test-all.sh @@ -0,0 +1,36 @@ +#! /bin/bash + +set -e + +dockerall="./gradlew ${DATABASE?}${MODE?}Compose" +dockerinfrastructure="./gradlew ${DATABASE?}${MODE?}infrastructureCompose" + +./gradlew testClasses + +${dockerall}Down -P removeContainers=true +${dockerinfrastructure}Up + +./gradlew -x :end-to-end-tests:test build + +#Testing db cli +if [ "${DATABASE}" == "mysql" ]; then + echo 'show databases;' | ./mysql-cli.sh -i +elif [ "${DATABASE}" == "postgres" ]; then + echo '\l' | ./postgres-cli.sh -i +elif [ "${DATABASE}" == "mssql" ]; then + ./mssql-cli.sh "SELECT name FROM master.sys.databases;" +else + echo "Unknown Database" + exit 99 +fi + + +${dockerall}Build +${dockerall}Up + +#Testing mongo cli +echo 'show dbs' | ./mongodb-cli.sh -i + +./gradlew :end-to-end-tests:cleanTest :end-to-end-tests:test + +${dockerall}Down -P removeContainers=true \ No newline at end of file diff --git a/build-and-test-all-mysql-binlog.sh b/build-and-test-all-mysql-binlog.sh new file mode 100755 index 0000000..961910e --- /dev/null +++ b/build-and-test-all-mysql-binlog.sh @@ -0,0 +1,9 @@ +#! /bin/bash + +set -e + +export DATABASE=mysql +export MODE=binlog +export READER=MySqlReader + +./_build-and-test-all.sh diff --git a/build-and-test-everything.sh b/build-and-test-everything.sh new file mode 100755 index 0000000..0fd3c6f --- /dev/null +++ b/build-and-test-everything.sh @@ -0,0 +1,16 @@ +#! /bin/bash -e + +set -o pipefail + +SCRIPTS="./build-and-test-all-*" + +date > build-and-test-everything.log + +for script in $SCRIPTS ; do + echo '****************************************** Running' $script + date >> build-and-test-everything.log + echo '****************************************** Running' $script >> build-and-test-everything.log + $script | tee -a build-and-test-everything.log +done + +echo 'Finished successfully!!!' diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..2155850 --- /dev/null +++ b/build.gradle @@ -0,0 +1,83 @@ +buildscript { + repositories { + jcenter() + mavenCentral() + mavenLocal() + } + dependencies { + classpath "org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}" + classpath "com.avast.gradle:gradle-docker-compose-plugin:0.12.0" + } +} + +apply plugin: 'docker-compose' + +subprojects { + + apply plugin: "java" + + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + + repositories { + mavenLocal() + mavenCentral() + jcenter() + maven { + url "https://dl.bintray.com/eventuateio-oss/eventuate-maven-release" + } + eventuateMavenRepoUrl.split(',').each { repoUrl -> maven { url repoUrl } } + } + + dependencies { + implementation(platform("io.eventuate.platform:eventuate-platform-dependencies:$eventuatePlatformVersion")) + testCompile "junit:junit:4.12" + } + +} + +dockerCompose { + environment.put "EVENTUATE_COMMON_VERSION", eventuateCommonImageVersion + environment.put "EVENTUATE_CDC_VERSION", eventuateCdcImageVersion + environment.put "EVENTUATE_CDC_KAFKA_ENABLE_BATCH_PROCESSING", eventuateCdcKafkaEnableBatchProcessing + environment.put "EVENTUATE_JAVA_BASE_IMAGE_VERSION", eventuateExamplesBaseImageVersion + environment.put "EVENTUATE_PROXY_VERSION", eventuateProxyImageVersion + + removeOrphans = true + + mysqlbinlog { + projectName = null + useComposeFiles = ["docker-compose-mysql-binlog.yml"] + removeContainers = project.ext.removeContainers + } + + mysqlbinloginfrastructure { + projectName = null + useComposeFiles = ["docker-compose-mysql-binlog.yml"] + startedServices = ["cdc-service", "zipkin"] + removeContainers = project.ext.removeContainers + } +} + +subprojects.each { + if (it.name.endsWith("-service") || it.name.endsWith("-gateway")) { + mysqlbinlogComposeUp.dependsOn(":" + it.name + ":assemble") + } + } + +mysqlbinlogComposeUp.dependsOn(mysqlbinloginfrastructureComposeUp) + +task buildAndStartServicesMySql(type: GradleBuild) { + tasks = ["mysqlbinlogComposeUp"] +} + +task endToEndTests(type: GradleBuild) { + tasks = [":end-to-end-tests:test"] +} + +endToEndTests.dependsOn(mysqlbinlogComposeUp) +endToEndTests.dependsOn(":end-to-end-tests:cleanTest") + +task stopServicesMySql(type: GradleBuild) { + tasks = ["mysqlbinlogComposeDown"] +} diff --git a/buildSrc/src/main/groovy/ServicePlugin.groovy b/buildSrc/src/main/groovy/ServicePlugin.groovy new file mode 100644 index 0000000..5aa3c2d --- /dev/null +++ b/buildSrc/src/main/groovy/ServicePlugin.groovy @@ -0,0 +1,29 @@ +import org.gradle.api.Plugin +import org.gradle.api.Project + +class ServicePlugin implements Plugin { + + @Override + void apply(Project project) { + + project.apply(plugin: 'org.springframework.boot') + project.apply(plugin: "io.spring.dependency-management") + + project.dependencyManagement { + imports { + mavenBom "org.springframework.cloud:spring-cloud-starter-sleuth:${project.ext.springCloudSleuthVersion}" + } + } + + + project.dependencies { + + compile 'org.springframework.cloud:spring-cloud-starter-sleuth' + compile 'org.springframework.cloud:spring-cloud-starter-zipkin:2.2.8.RELEASE' + compile 'io.zipkin.brave:brave-bom:4.17.1' + + compile "io.eventuate.tram.core:eventuate-tram-spring-cloud-sleuth-integration" + } + + } +} diff --git a/common-swagger/build.gradle b/common-swagger/build.gradle new file mode 100755 index 0000000..8ae9691 --- /dev/null +++ b/common-swagger/build.gradle @@ -0,0 +1,3 @@ +dependencies { + compile "io.eventuate.util:eventuate-util-spring-swagger:$eventuateUtilVersion" +} diff --git a/common-swagger/src/main/java/io/eventuate/examples/tram/ordersandcustomers/commonswagger/CommonSwaggerConfiguration.java b/common-swagger/src/main/java/io/eventuate/examples/tram/ordersandcustomers/commonswagger/CommonSwaggerConfiguration.java new file mode 100755 index 0000000..3e0c528 --- /dev/null +++ b/common-swagger/src/main/java/io/eventuate/examples/tram/ordersandcustomers/commonswagger/CommonSwaggerConfiguration.java @@ -0,0 +1,13 @@ +package io.eventuate.examples.tram.ordersandcustomers.commonswagger; + +import io.eventuate.util.spring.swagger.EventuateSwaggerConfig; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class CommonSwaggerConfiguration { + @Bean + public EventuateSwaggerConfig eventuateSwaggerConfig() { + return () -> "io.eventuate.examples.tram.ordersandcustomers"; + } +} diff --git a/common/build.gradle b/common/build.gradle new file mode 100644 index 0000000..da3505e --- /dev/null +++ b/common/build.gradle @@ -0,0 +1,4 @@ +dependencies { + compile "io.eventuate.tram.core:eventuate-tram-events" + compile "commons-lang:commons-lang:2.6" +} \ No newline at end of file diff --git a/common/src/main/java/io/eventuate/examples/tram/ordersandcustomers/common/domain/Money.java b/common/src/main/java/io/eventuate/examples/tram/ordersandcustomers/common/domain/Money.java new file mode 100644 index 0000000..76e04d5 --- /dev/null +++ b/common/src/main/java/io/eventuate/examples/tram/ordersandcustomers/common/domain/Money.java @@ -0,0 +1,61 @@ +package io.eventuate.examples.tram.ordersandcustomers.common.domain; + +import org.apache.commons.lang.builder.EqualsBuilder; +import org.apache.commons.lang.builder.HashCodeBuilder; +import org.apache.commons.lang.builder.ToStringBuilder; + +import java.math.BigDecimal; + +public class Money { + + public static final Money ZERO = new Money(0); + private BigDecimal amount; + + public Money() { + } + + public Money(int i) { + this.amount = new BigDecimal(i); + } + public Money(String s) { + this.amount = new BigDecimal(s); + } + + @Override + public String toString() { + return ToStringBuilder.reflectionToString(this); + } + + @Override + public boolean equals(Object obj) { + return EqualsBuilder.reflectionEquals(this, obj); + } + + @Override + public int hashCode() { + return HashCodeBuilder.reflectionHashCode(this); + } + + public Money(BigDecimal amount) { + this.amount = amount; + } + + public BigDecimal getAmount() { + return amount; + } + + public void setAmount(BigDecimal amount) { + this.amount = amount; + } + + public boolean isGreaterThanOrEqual(Money other) { + return amount.compareTo(other.amount) >= 0; + } + + public Money add(Money other) { + return new Money(amount.add(other.amount)); + } + public Money subtract(Money other) { + return new Money(amount.subtract(other.amount)); + } +} \ No newline at end of file diff --git a/customer-service-api-messaging/build.gradle b/customer-service-api-messaging/build.gradle new file mode 100644 index 0000000..a286f4a --- /dev/null +++ b/customer-service-api-messaging/build.gradle @@ -0,0 +1,4 @@ +dependencies { + compile project(":common") +} + diff --git a/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/AbstractCustomerOrderEvent.java b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/AbstractCustomerOrderEvent.java new file mode 100644 index 0000000..d20a3e3 --- /dev/null +++ b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/AbstractCustomerOrderEvent.java @@ -0,0 +1,20 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.domain.events; + +public abstract class AbstractCustomerOrderEvent implements CustomerEvent { + protected Long orderId; + + protected AbstractCustomerOrderEvent(Long orderId) { + this.orderId = orderId; + } + + protected AbstractCustomerOrderEvent() { + } + + public Long getOrderId() { + return orderId; + } + + public void setOrderId(Long orderId) { + this.orderId = orderId; + } +} diff --git a/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreatedEvent.java b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreatedEvent.java new file mode 100644 index 0000000..e9d7a3c --- /dev/null +++ b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreatedEvent.java @@ -0,0 +1,32 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.domain.events; + +import io.eventuate.examples.tram.ordersandcustomers.common.domain.Money; + +public class CustomerCreatedEvent implements CustomerEvent { + private String name; + private Money creditLimit; + + public CustomerCreatedEvent() { + } + + public CustomerCreatedEvent(String name, Money creditLimit) { + this.name = name; + this.creditLimit = creditLimit; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Money getCreditLimit() { + return creditLimit; + } + + public void setCreditLimit(Money creditLimit) { + this.creditLimit = creditLimit; + } +} diff --git a/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReleasedEvent.java b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReleasedEvent.java new file mode 100644 index 0000000..8475e26 --- /dev/null +++ b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReleasedEvent.java @@ -0,0 +1,11 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.domain.events; + +public class CustomerCreditReleasedEvent extends AbstractCustomerOrderEvent { + + public CustomerCreditReleasedEvent() { + } + + public CustomerCreditReleasedEvent(Long orderId) { + super(orderId); + } +} diff --git a/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReservationFailedEvent.java b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReservationFailedEvent.java new file mode 100644 index 0000000..2cfc129 --- /dev/null +++ b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReservationFailedEvent.java @@ -0,0 +1,13 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.domain.events; + +public class CustomerCreditReservationFailedEvent extends AbstractCustomerOrderEvent { + + public CustomerCreditReservationFailedEvent() { + } + + public CustomerCreditReservationFailedEvent(Long orderId) { + super(orderId); + } + + +} diff --git a/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReservedEvent.java b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReservedEvent.java new file mode 100644 index 0000000..dc9cd0f --- /dev/null +++ b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReservedEvent.java @@ -0,0 +1,11 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.domain.events; + +public class CustomerCreditReservedEvent extends AbstractCustomerOrderEvent { + + public CustomerCreditReservedEvent() { + } + + public CustomerCreditReservedEvent(Long orderId) { + super(orderId); + } +} diff --git a/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerEvent.java b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerEvent.java new file mode 100644 index 0000000..6c57fac --- /dev/null +++ b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerEvent.java @@ -0,0 +1,6 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.domain.events; + +import io.eventuate.tram.events.common.DomainEvent; + +public interface CustomerEvent extends DomainEvent { +} diff --git a/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerSnapshotEvent.java b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerSnapshotEvent.java new file mode 100644 index 0000000..fe8f098 --- /dev/null +++ b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerSnapshotEvent.java @@ -0,0 +1,43 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.domain.events; + +import io.eventuate.examples.tram.ordersandcustomers.common.domain.Money; +import io.eventuate.tram.events.common.DomainEvent; + +public class CustomerSnapshotEvent implements DomainEvent { + private Long id; + private String name; + private Money creditLimit; + + public CustomerSnapshotEvent() { + } + + public CustomerSnapshotEvent(Long id, String name, Money creditLimit) { + this.id = id; + this.name = name; + this.creditLimit = creditLimit; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Money getCreditLimit() { + return creditLimit; + } + + public void setCreditLimit(Money creditLimit) { + this.creditLimit = creditLimit; + } +} diff --git a/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerValidationFailedEvent.java b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerValidationFailedEvent.java new file mode 100644 index 0000000..6217f68 --- /dev/null +++ b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerValidationFailedEvent.java @@ -0,0 +1,11 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.domain.events; + +public class CustomerValidationFailedEvent extends AbstractCustomerOrderEvent { + + public CustomerValidationFailedEvent(Long orderId) { + super(orderId); + } + + public CustomerValidationFailedEvent() { + } +} diff --git a/customer-service-api-web/build.gradle b/customer-service-api-web/build.gradle new file mode 100644 index 0000000..368d0bc --- /dev/null +++ b/customer-service-api-web/build.gradle @@ -0,0 +1,3 @@ +dependencies { + compile project(":common") +} diff --git a/customer-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/webapi/CreateCustomerRequest.java b/customer-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/webapi/CreateCustomerRequest.java new file mode 100644 index 0000000..f14f533 --- /dev/null +++ b/customer-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/webapi/CreateCustomerRequest.java @@ -0,0 +1,26 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.webapi; + +import io.eventuate.examples.tram.ordersandcustomers.common.domain.Money; + +public class CreateCustomerRequest { + private String name; + private Money creditLimit; + + public CreateCustomerRequest() { + } + + public CreateCustomerRequest(String name, Money creditLimit) { + + this.name = name; + this.creditLimit = creditLimit; + } + + + public String getName() { + return name; + } + + public Money getCreditLimit() { + return creditLimit; + } +} diff --git a/customer-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/webapi/CreateCustomerResponse.java b/customer-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/webapi/CreateCustomerResponse.java new file mode 100644 index 0000000..5b93aca --- /dev/null +++ b/customer-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/webapi/CreateCustomerResponse.java @@ -0,0 +1,21 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.webapi; + + +public class CreateCustomerResponse { + private Long customerId; + + public CreateCustomerResponse() { + } + + public CreateCustomerResponse(Long customerId) { + this.customerId = customerId; + } + + public Long getCustomerId() { + return customerId; + } + + public void setCustomerId(Long customerId) { + this.customerId = customerId; + } +} diff --git a/customer-service/Dockerfile b/customer-service/Dockerfile new file mode 100755 index 0000000..91ca841 --- /dev/null +++ b/customer-service/Dockerfile @@ -0,0 +1,3 @@ +ARG baseImageVersion +FROM eventuateio/eventuate-examples-docker-images-java-example-base-image:$baseImageVersion +COPY build/libs/customer-service-*.jar service.jar diff --git a/customer-service/build.gradle b/customer-service/build.gradle new file mode 100644 index 0000000..4132b29 --- /dev/null +++ b/customer-service/build.gradle @@ -0,0 +1,23 @@ +plugins { + id 'com.google.cloud.tools.jib' version '1.7.0' +} + +apply plugin: ServicePlugin + +dependencies { + compile project(":common-swagger") + compile project(":customer-service-api-web") + + compile project(":customer-service-api-messaging") + compile project(":order-service-api-messaging") + + compile "io.eventuate.tram.core:eventuate-tram-spring-reactive-events-publisher" + + compile 'mysql:mysql-connector-java:5.1.36' + + compile "org.springframework.boot:spring-boot-starter" + compile "org.springframework.boot:spring-boot-starter-actuator" + compile "org.springframework.boot:spring-boot-starter-webflux:$springBootVersion" +} + +jib.to.image = "eventuate-tram-examples-customers-and-orders-customer-service:${version}" diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/CustomerConfiguration.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/CustomerConfiguration.java new file mode 100644 index 0000000..35c0376 --- /dev/null +++ b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/CustomerConfiguration.java @@ -0,0 +1,33 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers; + +import io.eventuate.common.spring.jdbc.reactive.EventuateCommonReactiveMysqlConfiguration; +import io.eventuate.examples.tram.ordersandcustomers.customers.domain.CustomerRepository; +import io.eventuate.examples.tram.ordersandcustomers.customers.domain.ReservedCreditRepository; +import io.eventuate.examples.tram.ordersandcustomers.customers.service.CustomerService; +import io.eventuate.tram.spring.events.publisher.ReactiveDomainEventPublisher; +import io.eventuate.tram.spring.events.publisher.ReactiveTramEventsPublisherConfiguration; +import io.eventuate.tram.spring.messaging.producer.jdbc.reactive.ReactiveTramMessageProducerJdbcConfiguration; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories; +import org.springframework.transaction.reactive.TransactionalOperator; + +@Configuration +@Import({ReactiveTramMessageProducerJdbcConfiguration.class, + EventuateCommonReactiveMysqlConfiguration.class, + ReactiveTramEventsPublisherConfiguration.class}) +@EnableAutoConfiguration +@EnableR2dbcRepositories +public class CustomerConfiguration { + + @Bean + public CustomerService customerService(CustomerRepository customerRepository, + ReservedCreditRepository reservedCreditRepository, + ReactiveDomainEventPublisher domainEventPublisher, + TransactionalOperator transactionalOperator) { + + return new CustomerService(customerRepository, reservedCreditRepository, domainEventPublisher, transactionalOperator); + } +} diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/CustomerServiceMain.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/CustomerServiceMain.java new file mode 100755 index 0000000..d688a03 --- /dev/null +++ b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/CustomerServiceMain.java @@ -0,0 +1,14 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers; + +import io.eventuate.examples.tram.ordersandcustomers.customers.web.CustomerWebConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; + +@SpringBootApplication +@Import({CustomerConfiguration.class, CustomerWebConfiguration.class}) +public class CustomerServiceMain { + public static void main(String[] args) { + SpringApplication.run(CustomerServiceMain.class, args); + } +} diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/Customer.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/Customer.java new file mode 100644 index 0000000..b86cb6a --- /dev/null +++ b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/Customer.java @@ -0,0 +1,54 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.domain; + +import io.eventuate.examples.tram.ordersandcustomers.common.domain.Money; +import io.eventuate.examples.tram.ordersandcustomers.customers.domain.events.CustomerCreatedEvent; +import io.eventuate.tram.events.publisher.ResultWithEvents; +import org.springframework.data.annotation.Id; +import org.springframework.data.annotation.Version; +import org.springframework.data.relational.core.mapping.Table; + +import java.math.BigDecimal; + +import static java.util.Collections.singletonList; + +@Table("customer") +public class Customer { + + @Id + private Long id; + private String name; + + private BigDecimal creditLimit; + + private Long creationTime; + + @Version + private Long version; + + public Customer() { + } + + public Customer(String name, BigDecimal creditLimit) { + this.name = name; + this.creditLimit = creditLimit; + this.creationTime = System.currentTimeMillis(); + } + + public static ResultWithEvents create(String name, Money creditLimit) { + Customer customer = new Customer(name, creditLimit.getAmount()); + return new ResultWithEvents<>(customer, + singletonList(new CustomerCreatedEvent(customer.getName(), new Money(customer.getCreditLimit())))); + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public BigDecimal getCreditLimit() { + return creditLimit; + } +} diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/CustomerCreditLimitExceededException.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/CustomerCreditLimitExceededException.java new file mode 100644 index 0000000..81e0aa5 --- /dev/null +++ b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/CustomerCreditLimitExceededException.java @@ -0,0 +1,4 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.domain; + +public class CustomerCreditLimitExceededException extends RuntimeException { +} diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/CustomerRepository.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/CustomerRepository.java new file mode 100644 index 0000000..aca071c --- /dev/null +++ b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/CustomerRepository.java @@ -0,0 +1,6 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.domain; + +import org.springframework.data.repository.reactive.ReactiveCrudRepository; + +public interface CustomerRepository extends ReactiveCrudRepository { +} diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/ReservedCredit.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/ReservedCredit.java new file mode 100644 index 0000000..90dccce --- /dev/null +++ b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/ReservedCredit.java @@ -0,0 +1,60 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.domain; + +import org.springframework.data.annotation.Id; +import org.springframework.data.relational.core.mapping.Table; + +import java.math.BigDecimal; + +@Table("reserved_credit") +public class ReservedCredit { + + @Id + private Long id; + + private Long customerId; + + private Long orderId; + + private BigDecimal reservation; + + public ReservedCredit() { + } + + public ReservedCredit(Long customerId, Long orderId, BigDecimal reservation) { + this.customerId = customerId; + this.orderId = orderId; + this.reservation = reservation; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getCustomerId() { + return customerId; + } + + public void setCustomerId(Long customerId) { + this.customerId = customerId; + } + + public Long getOrderId() { + return orderId; + } + + public void setOrderId(Long orderId) { + this.orderId = orderId; + } + + public BigDecimal getReservation() { + return reservation; + } + + public void setReservation(BigDecimal reservation) { + this.reservation = reservation; + } +} diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/ReservedCreditRepository.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/ReservedCreditRepository.java new file mode 100644 index 0000000..2543f26 --- /dev/null +++ b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/ReservedCreditRepository.java @@ -0,0 +1,10 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.domain; + +import org.springframework.data.repository.reactive.ReactiveCrudRepository; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +public interface ReservedCreditRepository extends ReactiveCrudRepository { + Flux findAllByCustomerId(Long customerId); + Mono deleteByOrderId(Long orderId); +} diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/service/CustomerService.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/service/CustomerService.java new file mode 100644 index 0000000..cf66a33 --- /dev/null +++ b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/service/CustomerService.java @@ -0,0 +1,115 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.service; + +import io.eventuate.examples.tram.ordersandcustomers.common.domain.Money; +import io.eventuate.examples.tram.ordersandcustomers.customers.domain.Customer; +import io.eventuate.examples.tram.ordersandcustomers.customers.domain.CustomerRepository; +import io.eventuate.examples.tram.ordersandcustomers.customers.domain.ReservedCredit; +import io.eventuate.examples.tram.ordersandcustomers.customers.domain.ReservedCreditRepository; +import io.eventuate.examples.tram.ordersandcustomers.customers.domain.events.CustomerCreditReservationFailedEvent; +import io.eventuate.examples.tram.ordersandcustomers.customers.domain.events.CustomerCreditReservedEvent; +import io.eventuate.examples.tram.ordersandcustomers.customers.domain.events.CustomerValidationFailedEvent; +import io.eventuate.tram.events.publisher.ResultWithEvents; +import io.eventuate.tram.messaging.common.Message; +import io.eventuate.tram.spring.events.publisher.ReactiveDomainEventPublisher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.transaction.reactive.TransactionalOperator; +import reactor.core.publisher.Mono; + +import java.math.BigDecimal; +import java.util.Collections; +import java.util.List; + +public class CustomerService { + + private Logger logger = LoggerFactory.getLogger(getClass()); + + private CustomerRepository customerRepository; + + private ReservedCreditRepository reservedCreditRepository; + + private ReactiveDomainEventPublisher domainEventPublisher; + + private TransactionalOperator transactionalOperator; + + public CustomerService(CustomerRepository customerRepository, + ReservedCreditRepository reservedCreditRepository, + ReactiveDomainEventPublisher domainEventPublisher, + TransactionalOperator transactionalOperator) { + this.customerRepository = customerRepository; + this.reservedCreditRepository = reservedCreditRepository; + this.domainEventPublisher = domainEventPublisher; + this.transactionalOperator = transactionalOperator; + } + + public Mono createCustomer(String name, Money creditLimit) { + ResultWithEvents customerWithEvents = Customer.create(name, creditLimit); + + return customerRepository + .save(customerWithEvents.result) + .flatMap(customer -> domainEventPublisher.publish(Customer.class, customer.getId(), customerWithEvents.events).collectList().map(messages -> customer)) + .as(transactionalOperator::transactional); + } + + public Mono reserveCredit(long orderId, long customerId, Money orderTotal) { + + Mono possibleCustomer = customerRepository.findById(customerId); + + return possibleCustomer + .flatMap(customer -> reservedCreditRepository + .findAllByCustomerId(customerId) + .collectList() + .flatMap(reservedCredits -> handleCreditReservation(reservedCredits, customer, orderId, customerId, orderTotal))) + .switchIfEmpty(handleNotExistingCustomer(orderId, customerId)) + .as(transactionalOperator::transactional) + .doOnError(throwable -> logger.error("credit reservation failed", throwable)) + .then(); + } + + public Mono releaseCredit(long orderId, long customerId) { + return reservedCreditRepository + .deleteByOrderId(orderId).as(transactionalOperator::transactional) + .doOnError(throwable -> logger.error("credit releasing failed", throwable)) + .then(); + } + + private Mono> handleCreditReservation(List reservedCredits, Customer customer, long orderId, long customerId, Money orderTotal) { + BigDecimal currentReservations = + reservedCredits.stream().map(ReservedCredit::getReservation).reduce(BigDecimal.ZERO, BigDecimal::add); + if (currentReservations.add(orderTotal.getAmount()).compareTo(customer.getCreditLimit()) <= 0) { + logger.info("reserving credit (orderId: {}, customerId: {})", orderId, customerId); + + CustomerCreditReservedEvent customerCreditReservedEvent = + new CustomerCreditReservedEvent(orderId); + + Mono reservedCredit = + reservedCreditRepository.save(new ReservedCredit(customerId, orderId, orderTotal.getAmount())); + + return reservedCredit.flatMap(rc -> + domainEventPublisher + .publish(Customer.class, + customer.getId(), + Collections.singletonList(customerCreditReservedEvent)) + .collectList()); + } else { + logger.info("handling credit reservation failure (orderId: {}, customerId: {})", orderId, customerId); + + CustomerCreditReservationFailedEvent customerCreditReservationFailedEvent = + new CustomerCreditReservationFailedEvent(orderId); + + return domainEventPublisher.publish(Customer.class, + customer.getId(), + Collections.singletonList(customerCreditReservationFailedEvent)) + .collectList(); + } + } + + private Mono> handleNotExistingCustomer(long orderId, long customerId) { + return Mono.defer(() -> + domainEventPublisher + .publish(Customer.class, + customerId, + Collections.singletonList(new CustomerValidationFailedEvent(orderId))) + .collectList()); + } +} diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/web/CustomerController.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/web/CustomerController.java new file mode 100644 index 0000000..d25a93a --- /dev/null +++ b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/web/CustomerController.java @@ -0,0 +1,29 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.web; + +import io.eventuate.examples.tram.ordersandcustomers.customers.service.CustomerService; +import io.eventuate.examples.tram.ordersandcustomers.customers.webapi.CreateCustomerRequest; +import io.eventuate.examples.tram.ordersandcustomers.customers.webapi.CreateCustomerResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +@RestController +public class CustomerController { + + private CustomerService customerService; + + @Autowired + public CustomerController(CustomerService customerService) { + this.customerService = customerService; + } + + @RequestMapping(value = "/customers", method = RequestMethod.POST) + public Mono createCustomer(@RequestBody CreateCustomerRequest createCustomerRequest) { + return customerService + .createCustomer(createCustomerRequest.getName(), createCustomerRequest.getCreditLimit()) + .map(customer -> new CreateCustomerResponse(customer.getId())); + } +} diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/web/CustomerWebConfiguration.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/web/CustomerWebConfiguration.java new file mode 100755 index 0000000..276cb79 --- /dev/null +++ b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/web/CustomerWebConfiguration.java @@ -0,0 +1,12 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.web; + +import io.eventuate.examples.tram.ordersandcustomers.commonswagger.CommonSwaggerConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@ComponentScan +@Import(CommonSwaggerConfiguration.class) +public class CustomerWebConfiguration { +} \ No newline at end of file diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/web/OrderEventHttpConsumer.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/web/OrderEventHttpConsumer.java new file mode 100644 index 0000000..26ed5ec --- /dev/null +++ b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/web/OrderEventHttpConsumer.java @@ -0,0 +1,33 @@ +package io.eventuate.examples.tram.ordersandcustomers.customers.web; + +import io.eventuate.examples.tram.ordersandcustomers.customers.service.CustomerService; +import io.eventuate.examples.tram.ordersandcustomers.orders.domain.events.OrderCreatedEvent; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +@RestController +public class OrderEventHttpConsumer { + + private CustomerService customerService; + + @Autowired + public OrderEventHttpConsumer(CustomerService customerService) { + this.customerService = customerService; + } + + @RequestMapping(value = "/events/orderserviceevents/io.eventuate.examples.tram.ordersandcustomers.orders.domain.Order/{aggregateId}/io.eventuate.examples.tram.ordersandcustomers.orders.domain.events.OrderCreatedEvent/{eventId}", method = RequestMethod.POST) + public Mono handleOrderCreatedEvent(@RequestBody OrderCreatedEvent event, @PathVariable("aggregateId") String aggregateId) { + return customerService.reserveCredit(Long.parseLong(aggregateId), + event.getOrderDetails().getCustomerId(), event.getOrderDetails().getOrderTotal()); + } + + @RequestMapping(value = "/events/orderserviceevents/io.eventuate.examples.tram.ordersandcustomers.orders.domain.Order/{aggregateId}/io.eventuate.examples.tram.ordersandcustomers.orders.domain.events.OrderCanceledEvent/{eventId}", method = RequestMethod.POST) + public Mono handleOrderCanceledEvent(@RequestBody OrderCreatedEvent event, @PathVariable("aggregateId") String aggregateId) { + return customerService.releaseCredit(Long.parseLong(aggregateId), event.getOrderDetails().getCustomerId()); + } +} diff --git a/customer-service/src/main/resources/application.properties b/customer-service/src/main/resources/application.properties new file mode 100644 index 0000000..10ff1e9 --- /dev/null +++ b/customer-service/src/main/resources/application.properties @@ -0,0 +1,19 @@ +spring.application.name=customer-service + +cdc.service.url: http://localhost:8099 + +spring.sleuth.enabled=true +spring.sleuth.sampler.probability=1 +spring.zipkin.base.url=http://${DOCKER_HOST_IP:localhost}:9411/ + +eventuate.reactive.db.driver=mysql +eventuate.reactive.db.host=${DOCKER_HOST_IP:localhost} +eventuate.reactive.db.port=3306 +eventuate.reactive.db.username=mysqluser +eventuate.reactive.db.password=mysqlpw +eventuate.reactive.db.database=eventuate + +spring.r2dbc.url=r2dbc:mysql://${DOCKER_HOST_IP:localhost}:3306/eventuate +spring.r2dbc.username=mysqluser +spring.r2dbc.password=mysqluser +spring.r2dbc.initialization-mode=always \ No newline at end of file diff --git a/docker-compose-mysql-binlog.yml b/docker-compose-mysql-binlog.yml new file mode 100755 index 0000000..8f7e1d4 --- /dev/null +++ b/docker-compose-mysql-binlog.yml @@ -0,0 +1,159 @@ +version: '3' +services: + order-service: + image: eventuateexamples/eventuate-tram-examples-customers-and-orders-order-service:development-202104132017 + ports: + - "8081:8080" + depends_on: + - mysql + - kafka + - zookeeper + - cdc-service + environment: + SPRING_DATASOURCE_URL: jdbc:mysql://mysql/eventuate + SPRING_DATASOURCE_USERNAME: mysqluser + SPRING_DATASOURCE_PASSWORD: mysqlpw + SPRING_DATASOURCE_DRIVER_CLASS_NAME: com.mysql.jdbc.Driver + EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092 + CDC_SERVICE_URL: http://cdc-service:8080 + SPRING_SLEUTH_ENABLED: "true" + SPRING_SLEUTH_SAMPLER_PROBABILITY: 1 + SPRING_ZIPKIN_BASE_URL: http://zipkin:9411/ + EVENTUATE_OUTBOX_ID: 1 + + customer-service: + build: + context: ./customer-service/ + args: + baseImageVersion: ${EVENTUATE_JAVA_BASE_IMAGE_VERSION} + image: eventuateexamples/eventuate-tram-examples-customers-and-orders-customer-service:${DOCKER_IMAGE_TAG:-latest} + ports: + - "8082:8080" + depends_on: + - mysql + - kafka + - zookeeper + - cdc-service + environment: + CDC_SERVICE_URL: http://cdc-service:8080 + SPRING_SLEUTH_ENABLED: "true" + SPRING_SLEUTH_SAMPLER_PROBABILITY: 1 + SPRING_ZIPKIN_BASE_URL: http://zipkin:9411/ + EVENTUATE_OUTBOX_ID: 1 + EVENTUATE_REACTIVE_DB_DRIVER: mysql + EVENTUATE_REACTIVE_DB_HOST: mysql + EVENTUATE_REACTIVE_DB_PORT: 3306 + EVENTUATE_REACTIVE_DB_USERNAME: mysqluser + EVENTUATE_REACTIVE_DB_PASSWORD: mysqlpw + EVENTUATE_REACTIVE_DB_DATABASE: eventuate + SPRING_R2DBC_URL: r2dbc:mysql://mysql:3306/eventuate + SPRING_R2DBC_USERNAME: mysqluser + SPRING_R2DBC_PASSWORD: mysqluser + + order-history-service: + image: eventuateexamples/eventuate-tram-examples-customers-and-orders-order-history-service:development-202104132017 + ports: + - "8083:8080" + depends_on: + - mongodb + - kafka + - zookeeper + environment: + SPRING_DATA_MONGODB_URI: mongodb://mongodb/customers_and_orders + EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092 + SPRING_SLEUTH_ENABLED: "true" + SPRING_SLEUTH_SAMPLER_PROBABILITY: 1 + SPRING_ZIPKIN_BASE_URL: http://zipkin:9411/ + + zookeeper: + image: eventuateio/eventuate-zookeeper:$EVENTUATE_COMMON_VERSION + ports: + - 2181:2181 + + kafka: + image: "confluentinc/cp-kafka:5.2.4" + ports: + - 9092:9092 + - 29092:29092 + depends_on: + - zookeeper + environment: + KAFKA_LISTENERS: LC://kafka:29092,LX://kafka:9092 + KAFKA_ADVERTISED_LISTENERS: LC://kafka:29092,LX://${DOCKER_HOST_IP:-localhost}:9092 + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: LC:PLAINTEXT,LX:PLAINTEXT + KAFKA_INTER_BROKER_LISTENER_NAME: LC + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_HEAP_OPTS: -Xmx192m + + + mysql: + build: + context: ./mysql/ + args: + baseImageVersion: ${EVENTUATE_COMMON_VERSION} + ports: + - 3306:3306 + environment: + - MYSQL_ROOT_PASSWORD=rootpassword + - MYSQL_USER=mysqluser + - MYSQL_PASSWORD=mysqlpw + - USE_DB_ID=true + + mongodb: + image: mongo:3.6 + hostname: mongodb + command: mongod --smallfiles + ports: + - "27017:27017" + + cdc-service: + image: eventuateio/eventuate-cdc-service:$EVENTUATE_CDC_VERSION + ports: + - "8099:8080" + depends_on: + - mysql + - kafka + - zookeeper + environment: + SPRING_DATASOURCE_URL: jdbc:mysql://mysql/eventuate + SPRING_DATASOURCE_USERNAME: mysqluser + SPRING_DATASOURCE_PASSWORD: mysqlpw + SPRING_DATASOURCE_DRIVER_CLASS_NAME: com.mysql.jdbc.Driver + EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092 + EVENTUATELOCAL_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181 + EVENTUATELOCAL_CDC_DB_USER_NAME: root + EVENTUATELOCAL_CDC_DB_PASSWORD: rootpassword + EVENTUATELOCAL_CDC_READER_NAME: MySqlReader + EVENTUATELOCAL_CDC_MYSQL_BINLOG_CLIENT_UNIQUE_ID: 1234567890 + EVENTUATELOCAL_CDC_READ_OLD_DEBEZIUM_DB_OFFSET_STORAGE_TOPIC: "false" + EVENTUATE_CDC_KAFKA_ENABLE_BATCH_PROCESSING: ${EVENTUATE_CDC_KAFKA_ENABLE_BATCH_PROCESSING} + EVENTUATE_OUTBOX_ID: 1 + JAVA_OPTS: -Xmx64m + + proxy: + image: eventuateio/eventuate-tram-http-proxy-service:$EVENTUATE_PROXY_VERSION + ports: + - "8098:8080" + depends_on: + - mysql + - kafka + - zookeeper + environment: + EVENTUATELOCAL_KAFKA_BOOTSTRAP_SERVERS: kafka:29092 + EVENTUATE_HTTP_PROXY_ZOOKEEPER_CONNECTION_STRING: zookeeper:2181 + SPRING_DATASOURCE_URL: jdbc:mysql://mysql/eventuate + SPRING_DATASOURCE_USERNAME: mysqluser + SPRING_DATASOURCE_PASSWORD: mysqlpw + SPRING_DATASOURCE_DRIVER_CLASS_NAME: com.mysql.jdbc.Driver + EVENTUATE_LOCAL_KAFKA_CONSUMER_PROPERTIES_SESSION_TIMEOUT_MS: 6000 + EVENTUATE_SUBSCRIPTION_EVENT_ORDERSERVICEEVENTS_AGGREGATE: io.eventuate.examples.tram.ordersandcustomers.orders.domain.Order + EVENTUATE_SUBSCRIPTION_EVENT_ORDERSERVICEEVENTS_EVENTS: io.eventuate.examples.tram.ordersandcustomers.orders.domain.events.OrderCreatedEvent,io.eventuate.examples.tram.ordersandcustomers.orders.domain.events.OrderCanceledEvent + EVENTUATE_SUBSCRIPTION_EVENT_ORDERSERVICEEVENTS_BASEURL: http://customer-service:8080/events + + zipkin: + image: openzipkin/zipkin:2.21 + ports: + - "9411:9411" + environment: + JAVA_OPTS: -Xmx64m diff --git a/end-to-end-tests/build.gradle b/end-to-end-tests/build.gradle new file mode 100644 index 0000000..34f6d0e --- /dev/null +++ b/end-to-end-tests/build.gradle @@ -0,0 +1,11 @@ +dependencies { + compile project(":order-history-service-api-web") + compile project(":customer-service-api-web") + compile project(":order-service-api-web") + + testCompile "io.eventuate.util:eventuate-util-test:$eventuateUtilVersion" + testCompile "org.springframework.boot:spring-boot-starter-test:$springBootVersion" + testCompile "org.springframework.boot:spring-boot-starter-json:$springBootVersion" + +} + diff --git a/end-to-end-tests/src/test/java/io/eventuate/examples/tram/ordersandcustomers/endtoendtests/CustomersAndOrdersEndToEndTest.java b/end-to-end-tests/src/test/java/io/eventuate/examples/tram/ordersandcustomers/endtoendtests/CustomersAndOrdersEndToEndTest.java new file mode 100644 index 0000000..d0ce551 --- /dev/null +++ b/end-to-end-tests/src/test/java/io/eventuate/examples/tram/ordersandcustomers/endtoendtests/CustomersAndOrdersEndToEndTest.java @@ -0,0 +1,160 @@ +package io.eventuate.examples.tram.ordersandcustomers.endtoendtests; + +import io.eventuate.examples.tram.ordersandcustomers.common.domain.Money; +import io.eventuate.examples.tram.ordersandcustomers.customers.webapi.CreateCustomerRequest; +import io.eventuate.examples.tram.ordersandcustomers.customers.webapi.CreateCustomerResponse; +import io.eventuate.examples.tram.ordersandcustomers.orders.domain.events.OrderState; +import io.eventuate.examples.tram.ordersandcustomers.orderhistory.common.OrderInfo; +import io.eventuate.examples.tram.ordersandcustomers.orders.webapi.CreateOrderRequest; +import io.eventuate.examples.tram.ordersandcustomers.orders.webapi.CreateOrderResponse; +import io.eventuate.examples.tram.ordersandcustomers.orders.webapi.GetOrderResponse; +import io.eventuate.examples.tram.ordersandcustomers.orderhistory.common.CustomerView; +import io.eventuate.util.test.async.Eventually; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; +import org.springframework.web.client.RestTemplate; + +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThat; + +@RunWith(SpringJUnit4ClassRunner.class) +@SpringBootTest(classes = CustomersAndOrdersEndToEndTestConfiguration.class, webEnvironment = SpringBootTest.WebEnvironment.NONE) +public class CustomersAndOrdersEndToEndTest { + + @Value("${host.name}") + private String hostName; + + private String baseUrlOrders(String... path) { + StringBuilder sb = new StringBuilder(); + sb.append("http://").append(hostName).append(":8081"); + Arrays.stream(path).forEach(p -> { + sb.append('/').append(p); + }); + return sb.toString(); + } + + private String baseUrlCustomers(String path) { + return "http://"+hostName+":8082/" + path; + } + + private String baseUrlOrderHistory(String path) { + return "http://"+hostName+":8083/" + path; + } + + @Autowired + RestTemplate restTemplate; + + @Test + public void shouldApprove() { + Long customerId = createCustomer("Fred", new Money("15.00")); + Long orderId = createOrder(customerId, new Money("12.34")); + assertOrderState(orderId, OrderState.APPROVED); + } + + @Test + public void shouldReject() { + Long customerId = createCustomer("Fred", new Money("15.00")); + Long orderId = createOrder(customerId, new Money("123.34")); + assertOrderState(orderId, OrderState.REJECTED); + } + + @Test + public void shouldRejectForNonExistentCustomerId() { + Long customerId = System.nanoTime(); + Long orderId = createOrder(customerId, new Money("123.34")); + assertOrderState(orderId, OrderState.REJECTED); + } + + @Test + public void shouldCancel() { + Long customerId = createCustomer("Fred", new Money("15.00")); + Long orderId = createOrder(customerId, new Money("12.34")); + assertOrderState(orderId, OrderState.APPROVED); + cancelOrder(orderId); + assertOrderState(orderId, OrderState.CANCELLED); + + Eventually.eventually(120, 250, TimeUnit.MILLISECONDS, () -> { + CustomerView customerView = getCustomerView(customerId); + Map orders = customerView.getOrders(); + assertThat(orders.get(orderId).getState(), is(OrderState.CANCELLED)); + }); + + } + + @Test + public void shouldRejectApproveAndKeepOrdersInHistory() { + Long customerId = createCustomer("John", new Money("1000")); + + Long order1Id = createOrder(customerId, new Money("100")); + + assertOrderState(order1Id, OrderState.APPROVED); + + Long order2Id = createOrder(customerId, new Money("1000")); + + assertOrderState(order2Id, OrderState.REJECTED); + + + Eventually.eventually(120, 250, TimeUnit.MILLISECONDS, () -> { + CustomerView customerView = getCustomerView(customerId); + + Map orders = customerView.getOrders(); + + assertEquals(2, orders.size()); + + assertThat(orders.get(order1Id).getState(), is(OrderState.APPROVED)); + assertThat(orders.get(order2Id).getState(), is(OrderState.REJECTED)); + }); + } + + private CustomerView getCustomerView(Long customerId) { + String customerHistoryUrl = baseUrlOrderHistory("customers") + "/" + customerId; + ResponseEntity response = restTemplate.getForEntity(customerHistoryUrl, CustomerView.class); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + + Assert.assertNotNull(response); + + return response.getBody(); + } + + + private Long createCustomer(String name, Money credit) { + return restTemplate.postForObject(baseUrlCustomers("customers"), + new CreateCustomerRequest(name, credit), CreateCustomerResponse.class).getCustomerId(); + } + + private Long createOrder(Long customerId, Money orderTotal) { + return restTemplate.postForObject(baseUrlOrders("orders"), + new CreateOrderRequest(customerId, orderTotal), CreateOrderResponse.class).getOrderId(); + } + + private void cancelOrder(long orderId) { + restTemplate.postForObject(baseUrlOrders("orders", Long.toString(orderId), "cancel"), + null, GetOrderResponse.class); + } + + private void assertOrderState(Long id, OrderState expectedState) { + Eventually.eventually(120, 250, TimeUnit.MILLISECONDS, () -> { + ResponseEntity response = + restTemplate.getForEntity(baseUrlOrders("orders/" + id), GetOrderResponse.class); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + + GetOrderResponse order = response.getBody(); + + assertEquals(expectedState, order.getOrderState()); + }); + } +} diff --git a/end-to-end-tests/src/test/java/io/eventuate/examples/tram/ordersandcustomers/endtoendtests/CustomersAndOrdersEndToEndTestConfiguration.java b/end-to-end-tests/src/test/java/io/eventuate/examples/tram/ordersandcustomers/endtoendtests/CustomersAndOrdersEndToEndTestConfiguration.java new file mode 100755 index 0000000..96fd125 --- /dev/null +++ b/end-to-end-tests/src/test/java/io/eventuate/examples/tram/ordersandcustomers/endtoendtests/CustomersAndOrdersEndToEndTestConfiguration.java @@ -0,0 +1,30 @@ +package io.eventuate.examples.tram.ordersandcustomers.endtoendtests; + +import org.springframework.boot.autoconfigure.http.HttpMessageConverters; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; +import org.springframework.web.client.RestTemplate; + +import java.util.Arrays; +import java.util.List; + +@Configuration +public class CustomersAndOrdersEndToEndTestConfiguration { + + @Bean + public RestTemplate restTemplate(HttpMessageConverters converters) { + RestTemplate restTemplate = new RestTemplate(); + HttpMessageConverter httpMessageConverter = converters.getConverters().get(0); + List> httpMessageConverters = Arrays.asList(new MappingJackson2HttpMessageConverter()); + restTemplate.setMessageConverters((List>) httpMessageConverters); + return restTemplate; + } + + @Bean + public HttpMessageConverters customConverters() { + HttpMessageConverter additional = new MappingJackson2HttpMessageConverter(); + return new HttpMessageConverters(additional); + } +} diff --git a/end-to-end-tests/src/test/resources/application.properties b/end-to-end-tests/src/test/resources/application.properties new file mode 100755 index 0000000..ced8c3b --- /dev/null +++ b/end-to-end-tests/src/test/resources/application.properties @@ -0,0 +1 @@ +host.name=${DOCKER_HOST_IP:localhost} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..4f27961 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,25 @@ +org.gradle.jvmargs=-XX:MaxPermSize=512m + +deployUrl=file:///Users/cer/.m2/testdeploy + +eventuateMavenRepoUrl=https://dl.bintray.com/eventuateio-oss/eventuate-maven-release/,file:///Users/cer/.m2/testdeploy,https://dl.bintray.com/eventuateio-oss/eventuate-maven-rc,https://snapshots.repositories.eventuate.io/repository + +springBootVersion=2.4.5 +springCloudSleuthVersion=3.0.2 +eventuateUtilVersion=0.10.0.RELEASE +dockerComposePluginVersion=0.4.5 +version=0.1.0-SNAPSHOT + +eventuateTramViewSupportVersion=0.5.0.RELEASE + +eventuatePlatformVersion=2021.4.BUILD-SNAPSHOT + +eventuateCommonImageVersion=0.14.0.RELEASE +eventuateCdcImageVersion=0.12.0.RELEASE +eventuateCdcKafkaEnableBatchProcessing=false +removeContainers=false + +eventuateCommonVersion=0.15.0.BUILD-SNAPSHOT + +eventuateExamplesBaseImageVersion=BUILD-5 +eventuateProxyImageVersion=0.3.0.BUILD-SNAPSHOT diff --git a/gradle/gradlew b/gradle/gradlew new file mode 100755 index 0000000..91a7e26 --- /dev/null +++ b/gradle/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradle/gradlew.bat b/gradle/gradlew.bat new file mode 100755 index 0000000..aec9973 --- /dev/null +++ b/gradle/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100755 index 0000000000000000000000000000000000000000..9629357f1d50d72996e0d641bf31a1151ec82ac7 GIT binary patch literal 54783 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 DHRAVo literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..e61f9ec --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Thu Aug 17 21:50:31 PDT 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..4453cce --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save ( ) { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100755 index 0000000..e95643d --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/mongodb-cli.sh b/mongodb-cli.sh new file mode 100755 index 0000000..347d5d1 --- /dev/null +++ b/mongodb-cli.sh @@ -0,0 +1,3 @@ +#! /bin/bash + +docker run ${1:--it} --network=${PWD##*/}_default --rm mongo:3.6 sh -c "exec /usr/bin/mongo --host mongodb customers_and_orders" diff --git a/mssql-cli.sh b/mssql-cli.sh new file mode 100755 index 0000000..dfee51c --- /dev/null +++ b/mssql-cli.sh @@ -0,0 +1,9 @@ +#! /bin/bash -e + +docker run \ + --name mssqlterm --rm \ + --network=${PWD##*/}_default \ + -e MSSQL_HOST=mssql \ + -e QUERY="$1" \ + mcr.microsoft.com/mssql/server:2017-latest \ + sh -c 'exec /opt/mssql-tools/bin/sqlcmd -S "$MSSQL_HOST" -U SA -P "Eventuate123!" -Q "$QUERY"' diff --git a/mysql-cli.sh b/mysql-cli.sh new file mode 100755 index 0000000..c3a01f8 --- /dev/null +++ b/mysql-cli.sh @@ -0,0 +1,7 @@ +#! /bin/bash -e + +docker run ${1:--it} \ + --name mysqlterm --network=${PWD##*/}_default --rm \ + -e MYSQL_HOST=mysql \ + mysql:5.7.13 \ + sh -c 'exec mysql -h"$MYSQL_HOST" -uroot -prootpassword -o eventuate' diff --git a/mysql/8.initialize-database.sql b/mysql/8.initialize-database.sql new file mode 100644 index 0000000..4812350 --- /dev/null +++ b/mysql/8.initialize-database.sql @@ -0,0 +1,17 @@ +USE eventuate; + +CREATE TABLE customer ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + name VARCHAR(1024), + version BIGINT, + creation_time BIGINT, + credit_limit DECIMAL +); + +CREATE TABLE reserved_credit ( + id BIGINT AUTO_INCREMENT PRIMARY KEY, + customer_id BIGINT, + order_id BIGINT, + reservation DECIMAL, + FOREIGN KEY (customer_id) REFERENCES customer (id) +); \ No newline at end of file diff --git a/mysql/Dockerfile b/mysql/Dockerfile new file mode 100644 index 0000000..91ed7b4 --- /dev/null +++ b/mysql/Dockerfile @@ -0,0 +1,3 @@ +ARG baseImageVersion +FROM eventuateio/eventuate-mysql:$baseImageVersion +COPY 8.initialize-database.sql /docker-entrypoint-initdb.d diff --git a/order-history-service-api-web/build.gradle b/order-history-service-api-web/build.gradle new file mode 100644 index 0000000..f9ebbfc --- /dev/null +++ b/order-history-service-api-web/build.gradle @@ -0,0 +1,9 @@ +dependencies { + compile (project(":customer-service-api-messaging")) { + exclude module: "spring-boot-starter-data-jpa" + } + compile (project(":order-service-api-messaging")) { + exclude module: "spring-boot-starter-data-jpa" + } + compile "org.springframework.boot:spring-boot-starter-data-mongodb:$springBootVersion" +} diff --git a/order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/CustomerView.java b/order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/CustomerView.java new file mode 100644 index 0000000..f3a91de --- /dev/null +++ b/order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/CustomerView.java @@ -0,0 +1,48 @@ +package io.eventuate.examples.tram.ordersandcustomers.orderhistory.common; + +import io.eventuate.examples.tram.ordersandcustomers.common.domain.Money; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +import java.util.HashMap; +import java.util.Map; + +@Document +public class CustomerView { + + @Id + private Long id; + + + private Map orders = new HashMap<>(); + private String name; + private Money creditLimit; + + public void setId(Long id) { + this.id = id; + } + + public Long getId() { + return id; + } + + public Map getOrders() { + return orders; + } + + public void setName(String name) { + this.name = name; + } + + public String getName() { + return name; + } + + public void setCreditLimit(Money creditLimit) { + this.creditLimit = creditLimit; + } + + public Money getCreditLimit() { + return creditLimit; + } +} diff --git a/order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/OrderInfo.java b/order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/OrderInfo.java new file mode 100644 index 0000000..ab25734 --- /dev/null +++ b/order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/OrderInfo.java @@ -0,0 +1,37 @@ +package io.eventuate.examples.tram.ordersandcustomers.orderhistory.common; + +import io.eventuate.examples.tram.ordersandcustomers.common.domain.Money; +import io.eventuate.examples.tram.ordersandcustomers.orders.domain.events.OrderState; + +public class OrderInfo { + + private Long orderId; + private OrderState state; + private Money orderTotal; + + + public OrderInfo() { + } + + public OrderInfo(Long orderId, Money orderTotal) { + this.orderId = orderId; + this.orderTotal = orderTotal; + this.state = OrderState.PENDING; + } + + public void approve() { + state = OrderState.APPROVED; + } + + public void reject() { + state = OrderState.REJECTED; + } + + public Money getOrderTotal() { + return orderTotal; + } + + public OrderState getState() { + return state; + } +} diff --git a/order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/OrderView.java b/order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/OrderView.java new file mode 100644 index 0000000..0025cfc --- /dev/null +++ b/order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/OrderView.java @@ -0,0 +1,34 @@ +package io.eventuate.examples.tram.ordersandcustomers.orderhistory.common; + +import io.eventuate.examples.tram.ordersandcustomers.common.domain.Money; +import io.eventuate.examples.tram.ordersandcustomers.orders.domain.events.OrderState; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document +public class OrderView { + + @Id + private Long id; + + private OrderState state; + private Money orderTotal; + + + public OrderView() { + } + + public OrderView(Long id, Money orderTotal) { + this.id = id; + this.orderTotal = orderTotal; + this.state = OrderState.PENDING; + } + + public Money getOrderTotal() { + return orderTotal; + } + + public OrderState getState() { + return state; + } +} diff --git a/order-service-api-messaging/build.gradle b/order-service-api-messaging/build.gradle new file mode 100644 index 0000000..a286f4a --- /dev/null +++ b/order-service-api-messaging/build.gradle @@ -0,0 +1,4 @@ +dependencies { + compile project(":common") +} + diff --git a/order-service-api-messaging/pom.xml b/order-service-api-messaging/pom.xml new file mode 100644 index 0000000..52f9411 --- /dev/null +++ b/order-service-api-messaging/pom.xml @@ -0,0 +1,32 @@ + + + io.eventuate.tram.examples.customers.and.orders + eventuate-tram-examples-customers-and-orders + 0.1.0-SNAPSHOT + + 4.0.0 + order-service-api-messaging + jar + + + + io.eventuate.tram.examples.customers.and.orders + common + 0.1.0-SNAPSHOT + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + diff --git a/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderApprovedEvent.java b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderApprovedEvent.java new file mode 100644 index 0000000..095da69 --- /dev/null +++ b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderApprovedEvent.java @@ -0,0 +1,17 @@ +package io.eventuate.examples.tram.ordersandcustomers.orders.domain.events; + +public class OrderApprovedEvent implements OrderEvent { + + private OrderDetails orderDetails; + + public OrderApprovedEvent() { + } + + public OrderApprovedEvent(OrderDetails orderDetails) { + this.orderDetails = orderDetails; + } + + public OrderDetails getOrderDetails() { + return orderDetails; + } +} diff --git a/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCancelConfirmedEvent.java b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCancelConfirmedEvent.java new file mode 100644 index 0000000..c990b27 --- /dev/null +++ b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCancelConfirmedEvent.java @@ -0,0 +1,17 @@ +package io.eventuate.examples.tram.ordersandcustomers.orders.domain.events; + +public class OrderCancelConfirmedEvent implements OrderEvent { + + private OrderDetails orderDetails; + + public OrderCancelConfirmedEvent() { + } + + public OrderCancelConfirmedEvent(OrderDetails orderDetails) { + this.orderDetails = orderDetails; + } + + public OrderDetails getOrderDetails() { + return orderDetails; + } +} diff --git a/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCancelledEvent.java b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCancelledEvent.java new file mode 100644 index 0000000..49f5935 --- /dev/null +++ b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCancelledEvent.java @@ -0,0 +1,17 @@ +package io.eventuate.examples.tram.ordersandcustomers.orders.domain.events; + +public class OrderCancelledEvent implements OrderEvent { + + private OrderDetails orderDetails; + + public OrderCancelledEvent() { + } + + public OrderCancelledEvent(OrderDetails orderDetails) { + this.orderDetails = orderDetails; + } + + public OrderDetails getOrderDetails() { + return orderDetails; + } +} diff --git a/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCreatedEvent.java b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCreatedEvent.java new file mode 100644 index 0000000..85bfda2 --- /dev/null +++ b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCreatedEvent.java @@ -0,0 +1,17 @@ +package io.eventuate.examples.tram.ordersandcustomers.orders.domain.events; + +public class OrderCreatedEvent implements OrderEvent { + + private OrderDetails orderDetails; + + public OrderCreatedEvent() { + } + + public OrderCreatedEvent(OrderDetails orderDetails) { + this.orderDetails = orderDetails; + } + + public OrderDetails getOrderDetails() { + return orderDetails; + } +} diff --git a/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderDetails.java b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderDetails.java new file mode 100644 index 0000000..ad0b3c7 --- /dev/null +++ b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderDetails.java @@ -0,0 +1,26 @@ +package io.eventuate.examples.tram.ordersandcustomers.orders.domain.events; + +import io.eventuate.examples.tram.ordersandcustomers.common.domain.Money; + +public class OrderDetails { + + private Long customerId; + + private Money orderTotal; + + public OrderDetails() { + } + + public OrderDetails(Long customerId, Money orderTotal) { + this.customerId = customerId; + this.orderTotal = orderTotal; + } + + public Long getCustomerId() { + return customerId; + } + + public Money getOrderTotal() { + return orderTotal; + } +} diff --git a/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderEvent.java b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderEvent.java new file mode 100644 index 0000000..95e1062 --- /dev/null +++ b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderEvent.java @@ -0,0 +1,6 @@ +package io.eventuate.examples.tram.ordersandcustomers.orders.domain.events; + +import io.eventuate.tram.events.common.DomainEvent; + +public interface OrderEvent extends DomainEvent { +} diff --git a/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderRejectedEvent.java b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderRejectedEvent.java new file mode 100644 index 0000000..138c537 --- /dev/null +++ b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderRejectedEvent.java @@ -0,0 +1,17 @@ +package io.eventuate.examples.tram.ordersandcustomers.orders.domain.events; + +public class OrderRejectedEvent implements OrderEvent { + + private OrderDetails orderDetails; + + public OrderRejectedEvent() { + } + + public OrderRejectedEvent(OrderDetails orderDetails) { + this.orderDetails = orderDetails; + } + + public OrderDetails getOrderDetails() { + return orderDetails; + } +} diff --git a/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderSnapshotEvent.java b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderSnapshotEvent.java new file mode 100644 index 0000000..fc7f225 --- /dev/null +++ b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderSnapshotEvent.java @@ -0,0 +1,53 @@ +package io.eventuate.examples.tram.ordersandcustomers.orders.domain.events; + +import io.eventuate.examples.tram.ordersandcustomers.common.domain.Money; +import io.eventuate.tram.events.common.DomainEvent; + +public class OrderSnapshotEvent implements DomainEvent { + private Long id; + private Long customerId; + private Money orderTotal; + private OrderState orderState; + + public OrderSnapshotEvent() { + } + + public OrderSnapshotEvent(Long id, Long customerId, Money orderTotal, OrderState orderState) { + this.id = id; + this.customerId = customerId; + this.orderTotal = orderTotal; + this.orderState = orderState; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public Long getCustomerId() { + return customerId; + } + + public void setCustomerId(Long customerId) { + this.customerId = customerId; + } + + public Money getOrderTotal() { + return orderTotal; + } + + public void setOrderTotal(Money orderTotal) { + this.orderTotal = orderTotal; + } + + public OrderState getOrderState() { + return orderState; + } + + public void setOrderState(OrderState orderState) { + this.orderState = orderState; + } +} diff --git a/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderState.java b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderState.java new file mode 100644 index 0000000..ddd489b --- /dev/null +++ b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderState.java @@ -0,0 +1,3 @@ +package io.eventuate.examples.tram.ordersandcustomers.orders.domain.events; + +public enum OrderState { PENDING, APPROVED, REJECTED, CANCELLED } diff --git a/order-service-api-web/build.gradle b/order-service-api-web/build.gradle new file mode 100644 index 0000000..5ffdfb0 --- /dev/null +++ b/order-service-api-web/build.gradle @@ -0,0 +1,7 @@ +dependencies { + compile (project(":order-service-api-messaging")) { + exclude module: "spring-boot-starter-data-jpa" + } +} + + diff --git a/order-service-api-web/pom.xml b/order-service-api-web/pom.xml new file mode 100644 index 0000000..ca11617 --- /dev/null +++ b/order-service-api-web/pom.xml @@ -0,0 +1,33 @@ + + + io.eventuate.tram.examples.customers.and.orders + eventuate-tram-examples-customers-and-orders + 0.1.0-SNAPSHOT + + 4.0.0 + order-service-api-web + jar + order-service-api-web + + + + io.eventuate.tram.examples.customers.and.orders + order-service-api-messaging + 0.1.0-SNAPSHOT + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${java.version} + ${java.version} + + + + + diff --git a/order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/CreateOrderRequest.java b/order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/CreateOrderRequest.java new file mode 100644 index 0000000..37ed2af --- /dev/null +++ b/order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/CreateOrderRequest.java @@ -0,0 +1,25 @@ +package io.eventuate.examples.tram.ordersandcustomers.orders.webapi; + + +import io.eventuate.examples.tram.ordersandcustomers.common.domain.Money; + +public class CreateOrderRequest { + private Money orderTotal; + private Long customerId; + + public CreateOrderRequest() { + } + + public CreateOrderRequest(Long customerId, Money orderTotal) { + this.customerId = customerId; + this.orderTotal = orderTotal; + } + + public Money getOrderTotal() { + return orderTotal; + } + + public Long getCustomerId() { + return customerId; + } +} diff --git a/order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/CreateOrderResponse.java b/order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/CreateOrderResponse.java new file mode 100644 index 0000000..278031b --- /dev/null +++ b/order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/CreateOrderResponse.java @@ -0,0 +1,17 @@ +package io.eventuate.examples.tram.ordersandcustomers.orders.webapi; + + +public class CreateOrderResponse { + private Long orderId; + + public CreateOrderResponse() { + } + + public CreateOrderResponse(Long orderId) { + this.orderId = orderId; + } + + public Long getOrderId() { + return orderId; + } +} diff --git a/order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/GetOrderResponse.java b/order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/GetOrderResponse.java new file mode 100644 index 0000000..0d8dba4 --- /dev/null +++ b/order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/GetOrderResponse.java @@ -0,0 +1,33 @@ +package io.eventuate.examples.tram.ordersandcustomers.orders.webapi; + + +import io.eventuate.examples.tram.ordersandcustomers.orders.domain.events.OrderState; + +public class GetOrderResponse { + private Long orderId; + private OrderState orderState; + + public GetOrderResponse() { + } + + public GetOrderResponse(Long orderId, OrderState orderState) { + this.orderId = orderId; + this.orderState = orderState; + } + + public Long getOrderId() { + return orderId; + } + + public void setOrderId(Long orderId) { + this.orderId = orderId; + } + + public OrderState getOrderState() { + return orderState; + } + + public void setOrderState(OrderState orderState) { + this.orderState = orderState; + } +} diff --git a/postgres-cli.sh b/postgres-cli.sh new file mode 100755 index 0000000..6c08d9c --- /dev/null +++ b/postgres-cli.sh @@ -0,0 +1,7 @@ +#! /bin/bash -e + +docker run ${1:--it} \ + --name postgresterm --network=${PWD##*/}_default \ + -e POSTGRES_HOST=postgres \ + --rm postgres:9.6.5 \ + sh -c 'export PGPASSWORD=eventuate; exec psql -h "$POSTGRES_HOST" -U eventuate' diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..fec7217 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,14 @@ +include 'common' +include 'common-swagger' + +include 'customer-service-api-web' +include 'customer-service-api-messaging' +include 'customer-service' + +include 'order-service-api-web' +include 'order-service-api-messaging' + +include 'order-history-service-api-web' + +include 'end-to-end-tests' + diff --git a/wait-for-mysql.sh b/wait-for-mysql.sh new file mode 100755 index 0000000..7ebcf59 --- /dev/null +++ b/wait-for-mysql.sh @@ -0,0 +1,7 @@ +#! /bin/sh + +until (echo select 1 from dual | ./mysql-cli.sh -i > /dev/null) +do + echo sleeping for mysql + sleep 5 +done diff --git a/wait-for-services.sh b/wait-for-services.sh new file mode 100755 index 0000000..362919a --- /dev/null +++ b/wait-for-services.sh @@ -0,0 +1,34 @@ +#! /bin/bash + +done=false + +echo waiting for: $* + +host=${1?} +shift +health_url=${1?} +shift +ports=$* + +if [ -z "$ports" ] ; then + echo no ports + exit 99 +fi + +while [[ "$done" = false ]]; do + for port in $ports; do + curl --fail http://${host}:${port}/${health_url} >& /dev/null + if [[ "$?" -eq "0" ]]; then + done=true + else + done=false + break + fi + done + if [[ "$done" = true ]]; then + echo connected + break; + fi + echo -n . + sleep 1 +done From afe3d410dc2e98f67ae69423613fad5dd56cf6cc Mon Sep 17 00:00:00 2001 From: Artem Sidorkin Date: Wed, 28 Apr 2021 20:09:33 +0300 Subject: [PATCH 2/2] Changed permissions. --- .github/workflows/build.yml | 0 .github/workflows/dump-messages.sh | 0 .github/workflows/print-container-logs.sh | 0 .gitignore | 0 LICENSE.md | 0 README.adoc | 0 build.gradle | 0 buildSrc/src/main/groovy/ServicePlugin.groovy | 0 common/build.gradle | 0 .../examples/tram/ordersandcustomers/common/domain/Money.java | 0 customer-service-api-messaging/build.gradle | 0 .../customers/domain/events/AbstractCustomerOrderEvent.java | 0 .../customers/domain/events/CustomerCreatedEvent.java | 0 .../customers/domain/events/CustomerCreditReleasedEvent.java | 0 .../domain/events/CustomerCreditReservationFailedEvent.java | 0 .../customers/domain/events/CustomerCreditReservedEvent.java | 0 .../ordersandcustomers/customers/domain/events/CustomerEvent.java | 0 .../customers/domain/events/CustomerSnapshotEvent.java | 0 .../customers/domain/events/CustomerValidationFailedEvent.java | 0 customer-service-api-web/build.gradle | 0 .../customers/webapi/CreateCustomerRequest.java | 0 .../customers/webapi/CreateCustomerResponse.java | 0 customer-service/build.gradle | 0 .../tram/ordersandcustomers/customers/CustomerConfiguration.java | 0 .../tram/ordersandcustomers/customers/domain/Customer.java | 0 .../customers/domain/CustomerCreditLimitExceededException.java | 0 .../ordersandcustomers/customers/domain/CustomerRepository.java | 0 .../tram/ordersandcustomers/customers/domain/ReservedCredit.java | 0 .../customers/domain/ReservedCreditRepository.java | 0 .../ordersandcustomers/customers/service/CustomerService.java | 0 .../tram/ordersandcustomers/customers/web/CustomerController.java | 0 .../ordersandcustomers/customers/web/OrderEventHttpConsumer.java | 0 customer-service/src/main/resources/application.properties | 0 end-to-end-tests/build.gradle | 0 .../endtoendtests/CustomersAndOrdersEndToEndTest.java | 0 gradle/wrapper/gradle-wrapper.properties | 0 mysql/8.initialize-database.sql | 0 mysql/Dockerfile | 0 order-history-service-api-web/build.gradle | 0 .../tram/ordersandcustomers/orderhistory/common/CustomerView.java | 0 .../tram/ordersandcustomers/orderhistory/common/OrderInfo.java | 0 .../tram/ordersandcustomers/orderhistory/common/OrderView.java | 0 order-service-api-messaging/build.gradle | 0 order-service-api-messaging/pom.xml | 0 .../orders/domain/events/OrderApprovedEvent.java | 0 .../orders/domain/events/OrderCancelConfirmedEvent.java | 0 .../orders/domain/events/OrderCancelledEvent.java | 0 .../orders/domain/events/OrderCreatedEvent.java | 0 .../ordersandcustomers/orders/domain/events/OrderDetails.java | 0 .../tram/ordersandcustomers/orders/domain/events/OrderEvent.java | 0 .../orders/domain/events/OrderRejectedEvent.java | 0 .../orders/domain/events/OrderSnapshotEvent.java | 0 .../tram/ordersandcustomers/orders/domain/events/OrderState.java | 0 order-service-api-web/build.gradle | 0 order-service-api-web/pom.xml | 0 .../tram/ordersandcustomers/orders/webapi/CreateOrderRequest.java | 0 .../ordersandcustomers/orders/webapi/CreateOrderResponse.java | 0 .../tram/ordersandcustomers/orders/webapi/GetOrderResponse.java | 0 settings.gradle | 0 59 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .github/workflows/build.yml mode change 100644 => 100755 .github/workflows/dump-messages.sh mode change 100644 => 100755 .github/workflows/print-container-logs.sh mode change 100644 => 100755 .gitignore mode change 100644 => 100755 LICENSE.md mode change 100644 => 100755 README.adoc mode change 100644 => 100755 build.gradle mode change 100644 => 100755 buildSrc/src/main/groovy/ServicePlugin.groovy mode change 100644 => 100755 common/build.gradle mode change 100644 => 100755 common/src/main/java/io/eventuate/examples/tram/ordersandcustomers/common/domain/Money.java mode change 100644 => 100755 customer-service-api-messaging/build.gradle mode change 100644 => 100755 customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/AbstractCustomerOrderEvent.java mode change 100644 => 100755 customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreatedEvent.java mode change 100644 => 100755 customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReleasedEvent.java mode change 100644 => 100755 customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReservationFailedEvent.java mode change 100644 => 100755 customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReservedEvent.java mode change 100644 => 100755 customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerEvent.java mode change 100644 => 100755 customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerSnapshotEvent.java mode change 100644 => 100755 customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerValidationFailedEvent.java mode change 100644 => 100755 customer-service-api-web/build.gradle mode change 100644 => 100755 customer-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/webapi/CreateCustomerRequest.java mode change 100644 => 100755 customer-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/webapi/CreateCustomerResponse.java mode change 100644 => 100755 customer-service/build.gradle mode change 100644 => 100755 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/CustomerConfiguration.java mode change 100644 => 100755 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/Customer.java mode change 100644 => 100755 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/CustomerCreditLimitExceededException.java mode change 100644 => 100755 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/CustomerRepository.java mode change 100644 => 100755 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/ReservedCredit.java mode change 100644 => 100755 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/ReservedCreditRepository.java mode change 100644 => 100755 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/service/CustomerService.java mode change 100644 => 100755 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/web/CustomerController.java mode change 100644 => 100755 customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/web/OrderEventHttpConsumer.java mode change 100644 => 100755 customer-service/src/main/resources/application.properties mode change 100644 => 100755 end-to-end-tests/build.gradle mode change 100644 => 100755 end-to-end-tests/src/test/java/io/eventuate/examples/tram/ordersandcustomers/endtoendtests/CustomersAndOrdersEndToEndTest.java mode change 100644 => 100755 gradle/wrapper/gradle-wrapper.properties mode change 100644 => 100755 mysql/8.initialize-database.sql mode change 100644 => 100755 mysql/Dockerfile mode change 100644 => 100755 order-history-service-api-web/build.gradle mode change 100644 => 100755 order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/CustomerView.java mode change 100644 => 100755 order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/OrderInfo.java mode change 100644 => 100755 order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/OrderView.java mode change 100644 => 100755 order-service-api-messaging/build.gradle mode change 100644 => 100755 order-service-api-messaging/pom.xml mode change 100644 => 100755 order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderApprovedEvent.java mode change 100644 => 100755 order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCancelConfirmedEvent.java mode change 100644 => 100755 order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCancelledEvent.java mode change 100644 => 100755 order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCreatedEvent.java mode change 100644 => 100755 order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderDetails.java mode change 100644 => 100755 order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderEvent.java mode change 100644 => 100755 order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderRejectedEvent.java mode change 100644 => 100755 order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderSnapshotEvent.java mode change 100644 => 100755 order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderState.java mode change 100644 => 100755 order-service-api-web/build.gradle mode change 100644 => 100755 order-service-api-web/pom.xml mode change 100644 => 100755 order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/CreateOrderRequest.java mode change 100644 => 100755 order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/CreateOrderResponse.java mode change 100644 => 100755 order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/GetOrderResponse.java mode change 100644 => 100755 settings.gradle diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml old mode 100644 new mode 100755 diff --git a/.github/workflows/dump-messages.sh b/.github/workflows/dump-messages.sh old mode 100644 new mode 100755 diff --git a/.github/workflows/print-container-logs.sh b/.github/workflows/print-container-logs.sh old mode 100644 new mode 100755 diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/LICENSE.md b/LICENSE.md old mode 100644 new mode 100755 diff --git a/README.adoc b/README.adoc old mode 100644 new mode 100755 diff --git a/build.gradle b/build.gradle old mode 100644 new mode 100755 diff --git a/buildSrc/src/main/groovy/ServicePlugin.groovy b/buildSrc/src/main/groovy/ServicePlugin.groovy old mode 100644 new mode 100755 diff --git a/common/build.gradle b/common/build.gradle old mode 100644 new mode 100755 diff --git a/common/src/main/java/io/eventuate/examples/tram/ordersandcustomers/common/domain/Money.java b/common/src/main/java/io/eventuate/examples/tram/ordersandcustomers/common/domain/Money.java old mode 100644 new mode 100755 diff --git a/customer-service-api-messaging/build.gradle b/customer-service-api-messaging/build.gradle old mode 100644 new mode 100755 diff --git a/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/AbstractCustomerOrderEvent.java b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/AbstractCustomerOrderEvent.java old mode 100644 new mode 100755 diff --git a/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreatedEvent.java b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreatedEvent.java old mode 100644 new mode 100755 diff --git a/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReleasedEvent.java b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReleasedEvent.java old mode 100644 new mode 100755 diff --git a/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReservationFailedEvent.java b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReservationFailedEvent.java old mode 100644 new mode 100755 diff --git a/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReservedEvent.java b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerCreditReservedEvent.java old mode 100644 new mode 100755 diff --git a/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerEvent.java b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerEvent.java old mode 100644 new mode 100755 diff --git a/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerSnapshotEvent.java b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerSnapshotEvent.java old mode 100644 new mode 100755 diff --git a/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerValidationFailedEvent.java b/customer-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/events/CustomerValidationFailedEvent.java old mode 100644 new mode 100755 diff --git a/customer-service-api-web/build.gradle b/customer-service-api-web/build.gradle old mode 100644 new mode 100755 diff --git a/customer-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/webapi/CreateCustomerRequest.java b/customer-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/webapi/CreateCustomerRequest.java old mode 100644 new mode 100755 diff --git a/customer-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/webapi/CreateCustomerResponse.java b/customer-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/webapi/CreateCustomerResponse.java old mode 100644 new mode 100755 diff --git a/customer-service/build.gradle b/customer-service/build.gradle old mode 100644 new mode 100755 diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/CustomerConfiguration.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/CustomerConfiguration.java old mode 100644 new mode 100755 diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/Customer.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/Customer.java old mode 100644 new mode 100755 diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/CustomerCreditLimitExceededException.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/CustomerCreditLimitExceededException.java old mode 100644 new mode 100755 diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/CustomerRepository.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/CustomerRepository.java old mode 100644 new mode 100755 diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/ReservedCredit.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/ReservedCredit.java old mode 100644 new mode 100755 diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/ReservedCreditRepository.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/domain/ReservedCreditRepository.java old mode 100644 new mode 100755 diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/service/CustomerService.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/service/CustomerService.java old mode 100644 new mode 100755 diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/web/CustomerController.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/web/CustomerController.java old mode 100644 new mode 100755 diff --git a/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/web/OrderEventHttpConsumer.java b/customer-service/src/main/java/io/eventuate/examples/tram/ordersandcustomers/customers/web/OrderEventHttpConsumer.java old mode 100644 new mode 100755 diff --git a/customer-service/src/main/resources/application.properties b/customer-service/src/main/resources/application.properties old mode 100644 new mode 100755 diff --git a/end-to-end-tests/build.gradle b/end-to-end-tests/build.gradle old mode 100644 new mode 100755 diff --git a/end-to-end-tests/src/test/java/io/eventuate/examples/tram/ordersandcustomers/endtoendtests/CustomersAndOrdersEndToEndTest.java b/end-to-end-tests/src/test/java/io/eventuate/examples/tram/ordersandcustomers/endtoendtests/CustomersAndOrdersEndToEndTest.java old mode 100644 new mode 100755 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties old mode 100644 new mode 100755 diff --git a/mysql/8.initialize-database.sql b/mysql/8.initialize-database.sql old mode 100644 new mode 100755 diff --git a/mysql/Dockerfile b/mysql/Dockerfile old mode 100644 new mode 100755 diff --git a/order-history-service-api-web/build.gradle b/order-history-service-api-web/build.gradle old mode 100644 new mode 100755 diff --git a/order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/CustomerView.java b/order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/CustomerView.java old mode 100644 new mode 100755 diff --git a/order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/OrderInfo.java b/order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/OrderInfo.java old mode 100644 new mode 100755 diff --git a/order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/OrderView.java b/order-history-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orderhistory/common/OrderView.java old mode 100644 new mode 100755 diff --git a/order-service-api-messaging/build.gradle b/order-service-api-messaging/build.gradle old mode 100644 new mode 100755 diff --git a/order-service-api-messaging/pom.xml b/order-service-api-messaging/pom.xml old mode 100644 new mode 100755 diff --git a/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderApprovedEvent.java b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderApprovedEvent.java old mode 100644 new mode 100755 diff --git a/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCancelConfirmedEvent.java b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCancelConfirmedEvent.java old mode 100644 new mode 100755 diff --git a/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCancelledEvent.java b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCancelledEvent.java old mode 100644 new mode 100755 diff --git a/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCreatedEvent.java b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderCreatedEvent.java old mode 100644 new mode 100755 diff --git a/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderDetails.java b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderDetails.java old mode 100644 new mode 100755 diff --git a/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderEvent.java b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderEvent.java old mode 100644 new mode 100755 diff --git a/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderRejectedEvent.java b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderRejectedEvent.java old mode 100644 new mode 100755 diff --git a/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderSnapshotEvent.java b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderSnapshotEvent.java old mode 100644 new mode 100755 diff --git a/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderState.java b/order-service-api-messaging/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/domain/events/OrderState.java old mode 100644 new mode 100755 diff --git a/order-service-api-web/build.gradle b/order-service-api-web/build.gradle old mode 100644 new mode 100755 diff --git a/order-service-api-web/pom.xml b/order-service-api-web/pom.xml old mode 100644 new mode 100755 diff --git a/order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/CreateOrderRequest.java b/order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/CreateOrderRequest.java old mode 100644 new mode 100755 diff --git a/order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/CreateOrderResponse.java b/order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/CreateOrderResponse.java old mode 100644 new mode 100755 diff --git a/order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/GetOrderResponse.java b/order-service-api-web/src/main/java/io/eventuate/examples/tram/ordersandcustomers/orders/webapi/GetOrderResponse.java old mode 100644 new mode 100755 diff --git a/settings.gradle b/settings.gradle old mode 100644 new mode 100755