├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── AUTHORS ├── CHANGELOG.md ├── CONTRIBUTING.md ├── ESArchSmokeTest.postman_collection.json ├── LICENSE ├── NOTICE ├── README.md ├── bigpave-pcf.sh ├── bigpave-pws.sh ├── ci ├── pipeline.yml ├── run-tests.sh ├── tasks │ ├── archive │ │ ├── package-command-side.sh │ │ ├── package-command-side.yml │ │ ├── package-query-side.sh │ │ ├── package-query-side.yml │ │ ├── unit-test-command-side.sh │ │ ├── unit-test-command-side.yml │ │ ├── unit-test-common.sh │ │ ├── unit-test-common.yml │ │ ├── unit-test-query-side.sh │ │ └── unit-test-query-side.yml │ ├── build-trader-app.sh │ ├── build-trader-app.yml │ ├── build-trader-ui.sh │ ├── build-trader-ui.yml │ ├── build-trading-engine.sh │ ├── build-trading-engine.yml │ ├── e2e-test-both-sides.sh │ ├── e2e-test-both-sides.yml │ ├── smoke-test-trader-app.sh │ ├── smoke-test-trader-app.yml │ ├── smoke-test-trading-engine.sh │ └── smoke-test-trading-engine.yml └── tips.txt ├── config-server-setup.json ├── deploy-backend.sh ├── deploy-frontend.sh ├── discovery-server ├── pom.xml └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── discoveryserver │ │ │ └── DiscoveryServerApplication.java │ └── resources │ │ └── application.properties │ └── test │ └── java │ └── com │ └── example │ └── discoveryserver │ └── DiscoveryServerApplicationTests.java ├── http-api.http ├── images ├── AxonTrader-UI-001.png └── CQRS+EventSourcing-on-CloudFoundry.jpg ├── mvnw ├── mvnw.cmd ├── pave.sh ├── pom.xml ├── teardown.sh ├── trader-app-ui ├── .eslintignore ├── .eslintrc ├── .gitignore ├── README.md ├── Staticfile ├── manifest.yml ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── index.html │ └── manifest.json └── src │ ├── actions │ ├── company.js │ ├── company.test.js │ ├── home.js │ ├── home.test.js │ ├── portfolio.js │ └── portfolio.test.js │ ├── app.scss │ ├── assets │ ├── home-background.png │ └── home-foreground-image.png │ ├── components │ ├── Company │ │ ├── BuyOrder.js │ │ ├── Company.js │ │ ├── CompanyList.js │ │ ├── DataTable.js │ │ ├── SellOrder.js │ │ └── styles.css │ ├── Dashboard │ │ ├── Portfolio.js │ │ ├── TradeItemsContainer.js │ │ ├── TradeItemsTable.js │ │ ├── Transactions.js │ │ ├── index.js │ │ └── styles.css │ ├── Home │ │ ├── Banner.js │ │ ├── CredentialsTable.js │ │ ├── Header.js │ │ ├── SideBar.js │ │ ├── SideBarItem.js │ │ ├── credentials.js │ │ ├── img │ │ │ ├── fondo.jpg │ │ │ ├── fondo@2x.jpg │ │ │ └── fondo@3x.jpg │ │ ├── index.js │ │ └── styles.css │ └── Loader │ │ ├── index.js │ │ └── styles.css │ ├── constants.js │ ├── constants │ ├── companyActions.js │ ├── homeActions.js │ └── portfolioActions.js │ ├── containers │ ├── App │ │ ├── App.test.js │ │ ├── index.js │ │ └── sessionStorage.js │ ├── CompanyContainer.js │ ├── CompanyListContainer │ │ ├── CompanyListContainer.js │ │ └── styles.css │ ├── DashboardContainer.js │ ├── LoginContainer │ │ ├── index.js │ │ └── styles.css │ ├── NavbarContainer │ │ ├── index.js │ │ └── styles.css │ └── SecureRoute.js │ ├── index.css │ ├── index.js │ ├── reducers │ ├── company.js │ ├── company.test.js │ ├── home.js │ ├── home.test.js │ ├── index.js │ ├── portfolio.js │ └── portfolio.test.js │ ├── registerServiceWorker.js │ └── utils │ ├── animation.js │ ├── config.js │ ├── fetch.js │ └── transactions.js ├── trader-app ├── manifest.yml ├── pom.xml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── pivotal │ │ │ └── refarch │ │ │ └── cqrs │ │ │ └── trader │ │ │ ├── app │ │ │ ├── AmqpConfiguration.java │ │ │ ├── AppConfig.java │ │ │ ├── SecurityConfig.java │ │ │ ├── TraderAggregateDataInitializer.java │ │ │ ├── TraderApplication.java │ │ │ ├── command │ │ │ │ ├── company │ │ │ │ │ ├── Company.java │ │ │ │ │ └── CompanyOrderBookListener.java │ │ │ │ └── user │ │ │ │ │ ├── DigestUtils.java │ │ │ │ │ ├── PortfolioManagementUserListener.java │ │ │ │ │ └── User.java │ │ │ ├── controller │ │ │ │ ├── CommandController.java │ │ │ │ └── QueryController.java │ │ │ └── query │ │ │ │ ├── company │ │ │ │ ├── CompanyEventHandler.java │ │ │ │ ├── CompanyView.java │ │ │ │ └── CompanyViewRepository.java │ │ │ │ ├── orderbook │ │ │ │ ├── OrderBookEventHandler.java │ │ │ │ └── OrderBookViewRepository.java │ │ │ │ ├── orders │ │ │ │ ├── trades │ │ │ │ │ ├── OrderBookView.java │ │ │ │ │ └── OrderView.java │ │ │ │ └── transaction │ │ │ │ │ ├── TradeExecutedQueryRepository.java │ │ │ │ │ ├── TradeExecutedView.java │ │ │ │ │ └── TransactionView.java │ │ │ │ ├── portfolio │ │ │ │ ├── ItemEntry.java │ │ │ │ ├── PortfolioEventHandler.java │ │ │ │ ├── PortfolioView.java │ │ │ │ └── PortfolioViewRepository.java │ │ │ │ ├── transaction │ │ │ │ ├── TransactionEventHandler.java │ │ │ │ └── TransactionViewRepository.java │ │ │ │ └── users │ │ │ │ ├── UserEventHandler.java │ │ │ │ ├── UserView.java │ │ │ │ └── UserViewRepository.java │ │ │ └── coreapi │ │ │ ├── company │ │ │ ├── CompanyId.java │ │ │ ├── commands.kt │ │ │ ├── events.kt │ │ │ └── queries.kt │ │ │ ├── orders │ │ │ ├── OrderBookId.java │ │ │ ├── trades │ │ │ │ ├── OrderId.java │ │ │ │ ├── commands.kt │ │ │ │ ├── events.kt │ │ │ │ └── queries.kt │ │ │ ├── transaction │ │ │ │ ├── TransactionId.java │ │ │ │ ├── commands.kt │ │ │ │ ├── events.kt │ │ │ │ ├── queries.kt │ │ │ │ └── valueObjects.kt │ │ │ └── valueObjects.kt │ │ │ ├── portfolio │ │ │ ├── PortfolioId.java │ │ │ ├── cash │ │ │ │ ├── commands.kt │ │ │ │ └── events.kt │ │ │ ├── commands.kt │ │ │ ├── events.kt │ │ │ ├── queries.kt │ │ │ └── stock │ │ │ │ ├── commands.kt │ │ │ │ └── events.kt │ │ │ └── users │ │ │ ├── UserId.java │ │ │ ├── commands.kt │ │ │ ├── events.kt │ │ │ └── queries.kt │ └── resources │ │ ├── application.properties │ │ └── bootstrap.properties │ └── test │ ├── java │ └── io │ │ └── pivotal │ │ └── refarch │ │ └── cqrs │ │ └── trader │ │ └── app │ │ ├── command │ │ ├── company │ │ │ ├── CompanyOrderBookListenerTest.java │ │ │ └── CompanyTest.java │ │ └── user │ │ │ ├── PortfolioManagementUserListenerTest.java │ │ │ └── UserTest.java │ │ ├── contracts │ │ ├── CommandContractTest.java │ │ └── QueryContractTest.java │ │ ├── controller │ │ ├── CommandControllerTest.java │ │ ├── CreateCompanyApiIntegrationTest.java │ │ └── QueryControllerTest.java │ │ └── query │ │ ├── company │ │ └── CompanyEventHandlerTest.java │ │ ├── orderbook │ │ └── OrderBookEventHandlerIntegrationTest.java │ │ ├── portfolio │ │ ├── PortfolioEventHandlerTest.java │ │ └── PortfolioViewTest.java │ │ ├── transaction │ │ └── TransactionEventHandlerTest.java │ │ └── users │ │ └── UserEventHandlerTest.java │ └── resources │ └── contracts │ └── trader │ ├── command │ ├── postAddItemsToPortfolioCommandShouldReturnOk.groovy │ ├── postAddOrderBookToCompanyCommandShouldReturnOk.groovy │ ├── postAuthenticateUserCommandShouldReturnOk.groovy │ ├── postCancelCashReservationCommandShouldReturnOk.groovy │ ├── postCancelItemReservationForPortfolioCommandShouldReturnOk.groovy │ ├── postCancelTransactionCommandShouldReturnOk.groovy │ ├── postConfirmCashReservationCommandShouldReturnOk.groovy │ ├── postConfirmItemReservationForPortfolioCommandShouldReturnOk.groovy │ ├── postConfirmTransactionCommandShouldReturnOk.groovy │ ├── postCreateCompanyCommandShouldReturnOkAndUuid.groovy │ ├── postCreateOrderBookCommandShouldReturnOkAndUuid.groovy │ ├── postCreatePortfolioCommandShouldReturnOkAndUuid.groovy │ ├── postCreateUserCommandShouldReturnOkAndUuid.groovy │ ├── postDepositCashCommandShouldReturnOk.groovy │ ├── postExecutedTransactionCommandShouldReturnOk.groovy │ ├── postOfIncorrectCommandPayloadReturnsNotFound.groovy │ ├── postOfNoneExistingCommandReturnsNotFound.groovy │ ├── postReserveCashCommandShouldReturnOk.groovy │ ├── postReserveItemsCommandShouldReturnOk.groovy │ ├── postStartBuyTransactionCommandShouldReturnOkAndUuid.groovy │ ├── postStartSellTransactionCommandShouldReturnOkAndUuid.groovy │ └── postWithdrawCashCommandShouldReturnOk.groovy │ └── query │ ├── findAllCompaniesShouldReturnAllCompanies.groovy │ ├── findAllUsersQueryShouldReturnAllUsers.groovy │ ├── getCompanyByIdShouldReturnCompany.groovy │ ├── getCompanyByIdShouldReturnNotFoundForNonExistingId.groovy │ ├── getExecutedTradesByOrderBookIdShouldNotFoundForNonExistingId.groovy │ ├── getExecutedTradesByOrderBookIdShouldReturnExecutedTrades.groovy │ ├── getOrderBookByIdShouldReturnNotFoundForNonExistingId.groovy │ ├── getOrderBookByIdShouldReturnOrderBook.groovy │ ├── getOrderBooksByCompanyIdShouldReturnNotFoundForNonExistingId.groovy │ ├── getOrderBooksByCompanyIdShouldReturnOrderBook.groovy │ ├── getPortfolioByIdShouldReturnNotFoundForNonExistingId.groovy │ ├── getPortfolioByIdShouldReturnPortfolio.groovy │ ├── getPortfolioByUserIdShouldReturnNotFoundForNonExistingId.groovy │ ├── getPortfolioByUserIdShouldReturnPortfolio.groovy │ ├── getTransactionByIdShouldReturnNotFoundForNonExistingId.groovy │ ├── getTransactionByIdShouldReturnTransaction.groovy │ ├── getTransactionsByPortfolioIdShouldReturnNotFoundForNonExistingId.groovy │ ├── getTransactionsByPortfolioIdShouldReturnTransactions.groovy │ ├── getUserByIdShouldReturnNotFoundForNonExistingId.groovy │ └── getUserByIdShouldReturnUser.groovy └── trading-engine ├── manifest.yml ├── pom.xml └── src ├── main ├── java │ └── io │ │ └── pivotal │ │ └── refarch │ │ └── cqrs │ │ └── trader │ │ ├── coreapi │ │ ├── orders │ │ │ ├── OrderBookId.java │ │ │ ├── trades │ │ │ │ ├── OrderId.java │ │ │ │ ├── commands.kt │ │ │ │ └── events.kt │ │ │ ├── transaction │ │ │ │ ├── TransactionId.java │ │ │ │ ├── commands.kt │ │ │ │ └── events.kt │ │ │ └── valueObjects.kt │ │ ├── portfolio │ │ │ ├── PortfolioId.java │ │ │ ├── cash │ │ │ │ ├── commands.kt │ │ │ │ └── events.kt │ │ │ ├── commands.kt │ │ │ ├── events.kt │ │ │ └── stock │ │ │ │ ├── commands.kt │ │ │ │ └── events.kt │ │ └── users │ │ │ └── UserId.java │ │ └── tradingengine │ │ ├── AmqpConfiguration.java │ │ ├── SecurityConfig.java │ │ ├── TradingEngineApplication.java │ │ ├── order │ │ ├── BuyTradeManagerSaga.java │ │ ├── Portfolio.java │ │ ├── SellTradeManagerSaga.java │ │ └── Transaction.java │ │ └── trade │ │ ├── Order.java │ │ └── OrderBook.java └── resources │ ├── application.properties │ └── bootstrap.properties └── test ├── java └── io │ └── pivotal │ └── refarch │ └── cqrs │ └── trader │ └── tradingengine │ ├── order │ ├── BuyTradeManagerSagaTest.java │ ├── PortfolioTest.java │ ├── SellTradeManagerSagaTest.java │ └── TransactionTest.java │ └── trade │ └── OrderBookTest.java └── resources └── bootstrap.properties /.gitignore: -------------------------------------------------------------------------------- 1 | .cf/ 2 | .idea/ 3 | .cf/ 4 | .DS_Store 5 | *.class 6 | **/private* 7 | **/.classpath 8 | **/.project 9 | **/.settings 10 | **/out 11 | 12 | # Node 13 | **/node_modules 14 | 15 | # Package Files # 16 | **/target/ 17 | **/bin 18 | **/*.iml 19 | axoniq-conference-demo.md 20 | 21 | **/build 22 | 23 | secret* -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pivotalsoftware/ESarch/c6e2f272b3cb99b414bb1e1b0456c9d0f3efe359/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.5.4/apache-maven-3.5.4-bin.zip -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | 2 | # This is the list of authors for copyright purposes. 3 | # 4 | # This does not necessarily list everyone who has contributed code, since in 5 | # some cases, their employer may be the copyright holder. To see the full list 6 | # of contributors, see the revision history in source control. 7 | 8 | ## Pivotal Software, Inc. 9 | 10 | * Ben Wilcock 11 | * David Caron 12 | * Jakub Pilmon 13 | * Kenny Bastani 14 | * Pieter Humphrey 15 | 16 | ## AxonIQ 17 | 18 | * Allard Buijze 19 | * Steven van Beelen 20 | 21 | ## Solstice 22 | 23 | * Sampath Kunta -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ### 10-08-2018 Ben W. 4 | 5 | Added in the scaffolding necessary to get "producer" side Spring Cloud Contracts in the Trader-App. This means that there are some changes to the Trader-App `POM.xml` and new API contract specifications written in Groovy. These specs are in the `/test/resources/contracts` folder and sre organised into folders by consumer. Each file contains a single spec of a request-response interaction. These contracts are used in two ways: firstly they are used by the SCC plugin to generate JUnit tests for the API specified (find them in `target/generated-test-sources`), and secondly a "stubs" jar is created which contains the specifications needed for wiremock to be able to fake the same API. To generate and run these contract-based tests use `mvn test` to add the stubs jar to your local `.m2` folder, use `mvn install`. 6 | 7 | > Side note: notice that the JUnit tests generated by SCC extend the same base class (`BaseContractTest`). This base class must be provided and is configured via the plugin XML configuration in the `POM.xml`. 8 | 9 | Separated the Unit level tests from the Integration level tests (test that run SpringBoot, like `@SpringBootTest`, `@DataJpaTest`, `@WebMvcTest` etc.). This involved changes to the `POM.xml`. To benefit from this separation... 10 | 11 | * Run unit tests > `mvn test` 12 | * Run unit and integration tests > `mvn verify` -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Pivotal Projects 2 | 3 | We’d love to accept your patches and contributions to this project. Please review the following guidelines you'll need to follow in order to make a contribution. 4 | 5 | ## Contributor License Agreement 6 | 7 | All contributors to this project must have a signed Contributor License Agreement (**"CLA"**) on file with us. The CLA grants us the permissions we need to use and redistribute your contributions as part of the project; you or your employer retain the copyright to your contribution. Head over to https://cla.pivotal.io/ to see your current agreement(s) on file or to sign a new one. 8 | 9 | We generally only need you (or your employer) to sign our CLA once and once signed, you should be able to submit contributions to any Pivotal project. 10 | 11 | Note: if you would like to submit an "_obvious fix_" for something like a typo, formatting issue or spelling mistake, you may not need to sign the CLA. Please see our information on [obvious fixes](https://cla.pivotal.io/about#obvious-fix) for more details. 12 | 13 | ## Code reviews 14 | 15 | All submissions, including submissions by project members, require review and we use GitHub's pull requests for this purpose. Please consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) if you need more information about using pull requests. 16 | 17 | 18 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | [Event Sourcing Reference Architecture] 2 | 3 | Copyright (c) 2018-Present Pivotal Software, Inc. All Rights Reserved. 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | -------------------------------------------------------------------------------- /bigpave-pcf.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Use this script to pave the required services on a PCF environment other than PWS (such as an enterprise-deployed cluster) 3 | 4 | cf create-service p.mysql db-small enginedb 5 | cf create-service p.mysql db-small appdb 6 | cf create-service p.rabbitmq single-node-3.7 rabbit 7 | cf create-service p-service-registry standard registry 8 | cf create-service p-config-server standard config -c config-server-setup.json 9 | 10 | -------------------------------------------------------------------------------- /bigpave-pws.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Use this script to pave your PWS instance with the correct services 3 | 4 | set -eu 5 | 6 | if [ -z ${1+x} ] && [ -z ${2+x} ] && [ -z ${3+x} ] && [ -z ${4+x} ]; then 7 | echo "Usage: USERNAME PASSWORD ORG SPACE" 8 | exit 1 9 | fi 10 | 11 | export CF='command -v cf' 12 | if [[ $? != 0 ]]; then 13 | echo "I don't think the CF command line client (cf-cli) is installed." 14 | echo "Please see the instructions here: https://docs.cloudfoundry.org/cf-cli/install-go-cli.html" 15 | exit 1 16 | else 17 | echo $'\nYou have the CF command line client already; that\'s awesome!\n' 18 | fi 19 | 20 | export API="https://api.run.pivotal.io" 21 | echo "API: ${API}" 22 | 23 | if [ -z ${1+x} ]; then 24 | echo "I need your PWS username (email address) to continue." 25 | exit 1 26 | else 27 | echo "Username: $1" 28 | fi 29 | 30 | if [ -z ${2+x} ]; then 31 | echo "I need your PWS password to continue." 32 | exit 1 33 | else 34 | echo "Password is {SECRET}." 35 | fi 36 | 37 | if [ -z ${3+x} ]; then 38 | echo "I need your PWS ORG name to continue." 39 | exit 1 40 | else 41 | echo "Org: $3" 42 | fi 43 | 44 | if [ -z ${4+x} ]; then 45 | echo "I need your PWS SPACE name to continue." 46 | exit 1 47 | else 48 | echo "Space: $4" 49 | fi 50 | 51 | echo $'Logging in to PWS using the details provided...\n' 52 | 53 | cf login -a ${API} -u $1 -p $2 -o $3 -s $4 54 | 55 | if [[ $? != 0 ]]; then 56 | echo "Abort. There was a problem logging in to PWS with the details given. Please resolve the problem and try again." 57 | exit 1 58 | else 59 | echo $'\nWe\'re logged in. Super!\n' 60 | fi 61 | 62 | echo $'Now we\'ll create some services using the Cloud Foundry marketplace [#Awesome #NoOps]...\n' 63 | 64 | cf create-service cleardb amp enginedb 65 | cf create-service cleardb amp appdb 66 | cf create-service cloudamqp tiger rabbit 67 | cf create-service p-service-registry standard registry 68 | cf create-service p-config-server standard config -c config-server-setup.json 69 | 70 | echo $'\nNow we need to wait a few minutes until all five 71 | services show \"create succeeded\" as their status.' 72 | echo "This may take while, so I'll start a watch. Press CTRL+C any time to quit (the services will still be created)." 73 | read -p "Press [Enter] to start watching..." 74 | 75 | watch -n 1 "cf services" 76 | 77 | echo $'\nWe\'re done. You can now use the push.sh script provided to push your apps.' -------------------------------------------------------------------------------- /ci/run-tests.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | if [ -n $engineURL ] 6 | then 7 | export engineURL=https://esrefarch-demo-trading-engine.cfapps.io 8 | echo -e "Setting URL for the Trading Engine to ${engineURL}" 9 | fi 10 | 11 | if [ -n $appURL ] 12 | then 13 | export appURL=https://esrefarch-demo-trader-app.cfapps.io 14 | echo -e "Setting URL for the Trading App to ${appURL}" 15 | fi 16 | 17 | if [ -n $uiURL ] 18 | then 19 | export uiURL=https://esrefarch-demo-trader-ui.cfapps.io 20 | echo -e "Setting URL for the Trader UI to ${uiURL}" 21 | fi 22 | 23 | env URL="$engineURL" ./tasks/smoke-test-trading-engine.sh 24 | env URL="$appURL" ./tasks/smoke-test-trader-app.sh 25 | ./tasks/e2e-test-both-sides.sh -------------------------------------------------------------------------------- /ci/tasks/archive/package-command-side.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e +x 4 | 5 | pushd source-code 6 | echo "Packaging JAR" 7 | ./gradlew command-side:assemble 8 | popd 9 | 10 | jar_count=`find source-code/command-side/build/libs -type f -name *.jar | wc -l` 11 | 12 | if [ $jar_count -gt 1 ]; then 13 | echo "More than one jar found, don't know which one to deploy. Exiting :(" 14 | exit 1 15 | fi 16 | 17 | find source-code/command-side/build/libs -type f -name *.jar -exec cp "{}" package-output/pcf-command-side.jar \; 18 | 19 | echo "Done packaging" 20 | exit 0 -------------------------------------------------------------------------------- /ci/tasks/archive/package-command-side.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | 4 | image_resource: 5 | type: docker-image 6 | source: 7 | repository: java 8 | tag: "8" 9 | 10 | inputs: 11 | - name: source-code 12 | 13 | outputs: 14 | - name: package-output 15 | 16 | run: 17 | path: source-code/ci/tasks/package-command-side.sh 18 | -------------------------------------------------------------------------------- /ci/tasks/archive/package-query-side.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e +x 4 | 5 | pushd source-code 6 | echo "Packaging JAR" 7 | ./gradlew query-side:assemble 8 | popd 9 | 10 | jar_count=`find source-code/query-side/build/libs -type f -name *.jar | wc -l` 11 | 12 | if [ $jar_count -gt 1 ]; then 13 | echo "More than one jar found, don't know which one to deploy. Exiting :(" 14 | exit 1 15 | fi 16 | 17 | find source-code/query-side/build/libs -type f -name *.jar -exec cp "{}" package-output/pcf-query-side.jar \; 18 | 19 | echo "Done packaging" 20 | exit 0 -------------------------------------------------------------------------------- /ci/tasks/archive/package-query-side.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | 4 | image_resource: 5 | type: docker-image 6 | source: 7 | repository: java 8 | tag: "8" 9 | 10 | inputs: 11 | - name: source-code 12 | 13 | outputs: 14 | - name: package-output 15 | 16 | run: 17 | path: source-code/ci/tasks/package-command-side.sh 18 | -------------------------------------------------------------------------------- /ci/tasks/archive/unit-test-command-side.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | pushd source-code 6 | echo "Fetching Dependencies & Building Code..." 7 | ./gradlew command-side:assemble > /dev/null 8 | 9 | echo "Running Tests..." 10 | ./gradlew command-side:test 11 | popd 12 | 13 | exit 0 14 | -------------------------------------------------------------------------------- /ci/tasks/archive/unit-test-command-side.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | 4 | image_resource: 5 | type: docker-image 6 | source: 7 | repository: java 8 | tag: "8" 9 | 10 | inputs: 11 | - name: source-code 12 | 13 | run: 14 | path: source-code/ci/tasks/unit-test-command-side.sh 15 | -------------------------------------------------------------------------------- /ci/tasks/archive/unit-test-common.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | pushd source-code 6 | echo "Fetching Dependencies & Building Code..." 7 | ./gradlew common-api:assemble > /dev/null 8 | 9 | echo "Running Tests..." 10 | ./gradlew common-api:test 11 | popd 12 | 13 | exit 0 14 | -------------------------------------------------------------------------------- /ci/tasks/archive/unit-test-common.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | 4 | image_resource: 5 | type: docker-image 6 | source: 7 | repository: java 8 | tag: "8" 9 | 10 | inputs: 11 | - name: source-code 12 | 13 | run: 14 | path: source-code/ci/tasks/unit-test-common.sh 15 | -------------------------------------------------------------------------------- /ci/tasks/archive/unit-test-query-side.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -ex 4 | 5 | pushd source-code 6 | echo "Fetching Dependencies & Building Code..." 7 | ./gradlew query-side:assemble > /dev/null 8 | 9 | echo "Running Tests..." 10 | ./gradlew query-side:test 11 | popd 12 | 13 | exit 0 14 | -------------------------------------------------------------------------------- /ci/tasks/archive/unit-test-query-side.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | 4 | image_resource: 5 | type: docker-image 6 | source: 7 | repository: java 8 | tag: "8" 9 | 10 | inputs: 11 | - name: source-code 12 | 13 | run: 14 | path: source-code/ci/tasks/unit-test-query-side.sh 15 | -------------------------------------------------------------------------------- /ci/tasks/build-trader-app.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e +x 4 | 5 | export FOLDER=`pwd` 6 | echo "The path is ${OLDPATH}" 7 | 8 | echo "Testing and Packaging the trader App JAR..." 9 | cd source-code/trader-app 10 | mvn verify 11 | cd $FOLDER 12 | 13 | # jar_count=`find source-code/trader-app/target -type f -name *.jar | wc -l` 14 | 15 | # if [ $jar_count -gt 1 ]; then 16 | # echo "More than one jar found, don't know which one to deploy. Exiting :(" 17 | # exit 1 18 | # fi 19 | 20 | # find source-code/trader-app/target -type f -name *.jar -exec cp "{}" package-output/trader-app.jar \; 21 | 22 | find source-code/trader-app/target -type f -name trader-app.jar -exec cp "{}" package-output/trader-app.jar \; 23 | 24 | echo "Done packaging" 25 | exit 0 -------------------------------------------------------------------------------- /ci/tasks/build-trader-app.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | 4 | image_resource: 5 | type: docker-image 6 | source: 7 | repository: maven 8 | tag: "3.5-jdk-8-alpine" 9 | 10 | inputs: 11 | - name: source-code 12 | 13 | outputs: 14 | - name: package-output 15 | 16 | run: 17 | path: source-code/ci/tasks/build-trader-app.sh 18 | -------------------------------------------------------------------------------- /ci/tasks/build-trader-ui.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -eux 4 | 5 | export FOLDER=`pwd` 6 | echo "The path is ${FOLDER}" 7 | 8 | echo "Packaging the Trader UI App..." 9 | 10 | cd source-code/trader-app-ui 11 | npm install 12 | npm run build 13 | cp manifest.yml ${FOLDER}/package-output/manifest.yml 14 | cp Staticfile build/Staticfile 15 | cp -R build ${FOLDER}/package-output 16 | cd $FOLDER 17 | 18 | ls -la package-output 19 | ls -la package-output/build 20 | ls -la package-output/build/static 21 | 22 | echo "Done building trader ui" 23 | exit 0 -------------------------------------------------------------------------------- /ci/tasks/build-trader-ui.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | 4 | image_resource: 5 | type: docker-image 6 | source: 7 | repository: node 8 | 9 | inputs: 10 | - name: source-code 11 | 12 | outputs: 13 | - name: package-output 14 | 15 | run: 16 | path: source-code/ci/tasks/build-trader-ui.sh 17 | -------------------------------------------------------------------------------- /ci/tasks/build-trading-engine.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e +x 4 | 5 | export FOLDER=`pwd` 6 | echo "The path is ${OLDPATH}" 7 | 8 | echo "Testing and Packaging the Trading Engine JAR..." 9 | cd source-code/trading-engine 10 | mvn verify 11 | cd $FOLDER 12 | 13 | jar_count=`find source-code/trading-engine/target -type f -name *.jar | wc -l` 14 | 15 | if [ $jar_count -gt 1 ]; then 16 | echo "More than one jar found, don't know which one to deploy. Exiting :(" 17 | exit 1 18 | fi 19 | 20 | find source-code/trading-engine/target -type f -name *.jar -exec cp "{}" package-output/trading-engine.jar \; 21 | 22 | echo "Done packaging" 23 | exit 0 -------------------------------------------------------------------------------- /ci/tasks/build-trading-engine.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | 4 | image_resource: 5 | type: docker-image 6 | source: 7 | repository: maven 8 | tag: "3.5-jdk-8-alpine" 9 | 10 | inputs: 11 | - name: source-code 12 | 13 | outputs: 14 | - name: package-output 15 | 16 | run: 17 | path: source-code/ci/tasks/build-trading-engine.sh 18 | -------------------------------------------------------------------------------- /ci/tasks/e2e-test-both-sides.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | 4 | image_resource: 5 | type: docker-image 6 | source: 7 | repository: ubuntu 8 | tag: "latest" 9 | 10 | params: 11 | URL: 12 | 13 | inputs: 14 | - name: source-code 15 | 16 | run: 17 | path: source-code/ci/tasks/e2e-test-both-sides.sh -------------------------------------------------------------------------------- /ci/tasks/smoke-test-trader-app.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | apt-get update && apt-get install -y curl uuid-runtime jq --allow-unauthenticated 4 | 5 | set -eu 6 | 7 | echo "Smoke Testing the Trader App using the URL: ${URL}" 8 | 9 | # Begin the Smoke-testing... 10 | 11 | export HEALTH_STATUS=`curl -sL -X GET ${URL}/actuator/health | jq -r .status` 12 | if [ -z ${HEALTH_STATUS} ] || [ "$HEALTH_STATUS" != "UP" ] 13 | then 14 | echo -e "\e[31mError. The smoke test has failed, the application health check didn't work!" 15 | exit 1 16 | else 17 | echo "The health check status is reporting that ${URL} is ${HEALTH_STATUS}" 18 | fi 19 | 20 | export API=`curl -sL -X GET ${URL}/command/api | jq '. | length'` 21 | if [ -z ${API} ] || [[ ${API} < 1 ]] 22 | then 23 | echo -e "\e[31mError. The smoke test has failed, the application API command count was too low!" 24 | exit 1 25 | else 26 | echo "The API check status is reporting that there are ${API} commands available." 27 | fi 28 | 29 | echo -e "\e[32mTRADER-APP SMOKE TEST FINISHED - ZERO ERRORS ;D " 30 | exit 0 -------------------------------------------------------------------------------- /ci/tasks/smoke-test-trader-app.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | 4 | image_resource: 5 | type: docker-image 6 | source: 7 | repository: ubuntu 8 | tag: "latest" 9 | 10 | params: 11 | URL: 12 | 13 | inputs: 14 | - name: source-code 15 | 16 | run: 17 | path: source-code/ci/tasks/smoke-test-trader-app.sh -------------------------------------------------------------------------------- /ci/tasks/smoke-test-trading-engine.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | apt-get update && apt-get install -y curl uuid-runtime jq --allow-unauthenticated 4 | 5 | set -eu 6 | 7 | echo "Smoke Testing the Trading Engine using the URL: ${URL}" 8 | 9 | # Begin the Smoke-testing... 10 | 11 | export HEALTH_STATUS=`curl -sL -X GET ${URL}/actuator/health | jq -r .status` 12 | if [ -z $HEALTH_STATUS ] || [ "$HEALTH_STATUS" != "UP" ] 13 | then 14 | echo -e "\e[31mError. The smoke test has failed, the application health check didn't work!" 15 | exit 1 16 | else 17 | echo "The health check status is reporting that ${URL} is ${HEALTH_STATUS}" 18 | fi 19 | echo -e "\e[32mTRADING-ENGINE SMOKE TEST FINISHED - ZERO ERRORS ;D " 20 | exit 0 -------------------------------------------------------------------------------- /ci/tasks/smoke-test-trading-engine.yml: -------------------------------------------------------------------------------- 1 | --- 2 | platform: linux 3 | 4 | image_resource: 5 | type: docker-image 6 | source: 7 | repository: ubuntu 8 | tag: "latest" 9 | 10 | params: 11 | URL: 12 | 13 | inputs: 14 | - name: source-code 15 | 16 | run: 17 | path: source-code/ci/tasks/smoke-test-trading-engine.sh -------------------------------------------------------------------------------- /ci/tips.txt: -------------------------------------------------------------------------------- 1 | fly -t local login -c http://localhost:8000 2 | fly -t local set-pipeline -p pcf-cqrs-demo -c ci/pipeline.yml -l ci/private.yml 3 | fly -t local expose-pipeline -p pcf-cqrs-demo 4 | fly -t local unpause-pipeline -p pcf-cqrs-demo 5 | fly -t local trigger-job -j pcf-cqrs-demo/build-command-side 6 | fly -t local execute --config ci/tasks/build-command-side.yml --input source-code=. 7 | 8 | 9 | 10 | fly -t wings set-pipeline -p bw-esrefarch-demo -c ci/pipeline.yml -l ci/private.yml 11 | fly -t wings unpause-pipeline -p bw-esrefarch-demo 12 | fly -t wings expose-pipeline -p bw-esrefarch-demo 13 | fly -t wings containers 14 | fly -t wings trigger-job -j bw-esrefarch-demo/build-command-side 15 | 16 | ssh-keygen -t rsa -b 4096 -C "myemail@server.com" 17 | sh-agent -s 18 | ssh-add -K ~/.ssh/id_rsa 19 | pbcopy < ~/.ssh/id_rsa 20 | pbcopy < ~/.ssh/id_rsa.pub 21 | ssh -T git@github.com 22 | 23 | git stash 24 | git stash list 25 | git stash apply 26 | git merge redis-repo 27 | git push origin --delete redis-repo 28 | git fetch --all --prune 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /config-server-setup.json: -------------------------------------------------------------------------------- 1 | { 2 | "git": { 3 | "uri": "https://github.com/pivotalsoftware/ESarch-conf.git", 4 | "searchPaths": "{application}" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /deploy-backend.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # Use this script to package up your code 3 | 4 | set -eu 5 | 6 | pushd () { 7 | command pushd "$@" > /dev/null 8 | } 9 | 10 | popd () { 11 | command popd "$@" > /dev/null 12 | } 13 | 14 | if [ -z ${1+x} ] && [ -z ${2+x} ] && [ -z ${3+x} ] && [ -z ${4+x} ]; then 15 | echo "Usage: USERNAME PASSWORD ORG SPACE" 16 | exit 1 17 | fi 18 | 19 | export CF='command -v cf' 20 | if [[ $? != 0 ]]; then 21 | echo "I don't think the CF command line client (cf-cli) is installed." 22 | echo "Please see the instructions here: https://docs.cloudfoundry.org/cf-cli/install-go-cli.html" 23 | exit 1 24 | else 25 | echo $'\nYou have the CF command line client already; that\'s awesome!\n' 26 | fi 27 | 28 | export API="https://api.run.pivotal.io" 29 | echo "API: ${API}" 30 | 31 | 32 | if [ -z ${1+x} ]; then 33 | echo "I need your PWS username (email address) to continue." 34 | exit 1 35 | else 36 | echo "Username: $1" 37 | fi 38 | 39 | if [ -z ${2+x} ]; then 40 | echo "I need your PWS password to continue." 41 | exit 1 42 | else 43 | echo "Password is {SECRET}." 44 | fi 45 | 46 | if [ -z ${3+x} ]; then 47 | echo "I need your PWS ORG name to continue." 48 | exit 1 49 | else 50 | echo "Org: $3" 51 | fi 52 | 53 | if [ -z ${4+x} ]; then 54 | echo "I need your PWS SPACE name to continue." 55 | exit 1 56 | else 57 | echo "Space: $4" 58 | fi 59 | 60 | echo $'Logging in to PWS using the details provided...\n' 61 | 62 | cf login -a ${API} -u $1 -p $2 -o $3 -s $4 63 | 64 | if [[ $? != 0 ]]; then 65 | echo "Abort. There was a problem logging in to PWS with the details given. Please resolve the problem and try again." 66 | exit 1 67 | else 68 | echo $'\nWe\'re logged in. Let\'s deploy some code!\n' 69 | fi 70 | 71 | echo "Building the Java Packages..." 72 | mvn clean package -DskipTests=true 73 | 74 | echo "Pushing the Trading Engine to PWS..." 75 | pushd . 76 | cd trading-engine 77 | cf push -f manifest.yml --random-route 78 | popd 79 | 80 | echo "Pushing the Trader App to PWS..." 81 | pushd . 82 | cd trader-app 83 | cf push -f manifest.yml --random-route 84 | popd 85 | 86 | echo "Adding a network policy so that the Trader App and the Tradng Engine can communicate directly..." 87 | cf add-network-policy esrefarch-demo-trader-app --destination-app esrefarch-demo-trading-engine 88 | cf add-network-policy esrefarch-demo-trading-engine --destination-app esrefarch-demo-trader-app 89 | 90 | echo "All done. The Axon Trader application is now ready to test." 91 | -------------------------------------------------------------------------------- /deploy-frontend.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | # Use this script to package up your code 3 | 4 | set -eu 5 | 6 | pushd () { 7 | command pushd "$@" > /dev/null 8 | } 9 | 10 | popd () { 11 | command popd "$@" > /dev/null 12 | } 13 | 14 | if [ -z ${1+x} ] && [ -z ${2+x} ] && [ -z ${3+x} ] && [ -z ${4+x} ]; then 15 | echo "Usage: USERNAME PASSWORD ORG SPACE" 16 | exit 1 17 | fi 18 | 19 | export CF='command -v cf' 20 | if [[ $? != 0 ]]; then 21 | echo "I don't think the CF command line client (cf-cli) is installed." 22 | echo "Please see the instructions here: https://docs.cloudfoundry.org/cf-cli/install-go-cli.html" 23 | exit 1 24 | else 25 | echo $'\nYou have the CF command line client already; that\'s awesome!\n' 26 | fi 27 | 28 | export API="https://api.run.pivotal.io" 29 | echo "API: ${API}" 30 | 31 | 32 | if [ -z ${1+x} ]; then 33 | echo "I need your PWS username (email address) to continue." 34 | exit 1 35 | else 36 | echo "Username: $1" 37 | fi 38 | 39 | if [ -z ${2+x} ]; then 40 | echo "I need your PWS password to continue." 41 | exit 1 42 | else 43 | echo "Password is {SECRET}." 44 | fi 45 | 46 | if [ -z ${3+x} ]; then 47 | echo "I need your PWS ORG name to continue." 48 | exit 1 49 | else 50 | echo "Org: $3" 51 | fi 52 | 53 | if [ -z ${4+x} ]; then 54 | echo "I need your PWS SPACE name to continue." 55 | exit 1 56 | else 57 | echo "Space: $4" 58 | fi 59 | 60 | echo $'Logging in to PWS using the details provided...\n' 61 | 62 | cf login -a ${API} -u $1 -p $2 -o $3 -s $4 63 | 64 | if [[ $? != 0 ]]; then 65 | echo "Abort. There was a problem logging in to PWS with the details given. Please resolve the problem and try again." 66 | exit 1 67 | else 68 | echo $'\nWe\'re logged in. Let\'s deploy some code!\n' 69 | fi 70 | 71 | echo "Building the Trader UI..." 72 | pushd . 73 | cd trader-app-ui 74 | npm install 75 | npm run build 76 | echo "Pushing the Trader UI to PWS..." 77 | cf push -f manifest.yml --random-route 78 | popd 79 | 80 | echo "All done. The Axon Trader application is now ready to test." 81 | -------------------------------------------------------------------------------- /discovery-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.example 7 | discovery-server 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | discovery-server 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.3.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | Finchley.RELEASE 26 | 27 | 28 | 29 | 30 | org.springframework.cloud 31 | spring-cloud-starter-netflix-eureka-server 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-starter-test 37 | test 38 | 39 | 40 | 41 | 42 | 43 | 44 | org.springframework.cloud 45 | spring-cloud-dependencies 46 | ${spring-cloud.version} 47 | pom 48 | import 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | org.springframework.boot 57 | spring-boot-maven-plugin 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /discovery-server/src/main/java/com/example/discoveryserver/DiscoveryServerApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.discoveryserver; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | @EnableEurekaServer 8 | @SpringBootApplication 9 | public class DiscoveryServerApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(DiscoveryServerApplication.class, args); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /discovery-server/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8761 2 | -------------------------------------------------------------------------------- /discovery-server/src/test/java/com/example/discoveryserver/DiscoveryServerApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.example.discoveryserver; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class DiscoveryServerApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /http-api.http: -------------------------------------------------------------------------------- 1 | # For a quick start check out our HTTP Requests collection (Tools|HTTP Client|Open HTTP Requests Collection). 2 | # 3 | # Following HTTP Request Live Templates are available: 4 | # * 'gtrp' and 'gtr' create a GET request with or without query parameters; 5 | # * 'ptr' and 'ptrp' create a POST request with a simple or parameter-like body; 6 | # * 'mptr' and 'fptr' create a POST request to submit a form with a text or file field (multipart/form-data); 7 | 8 | 9 | GET http://localhost:8080/command/api 10 | 11 | ### 12 | 13 | POST https://esrefarch-demo-trader-app.cfapps.io/command/CreateCompanyCommand 14 | Content-Type: application/json 15 | 16 | { 17 | "companyId": "fa8e9411-9e3a-46a3-9751-14ff68824fe7", 18 | "userId": "1bae0365-949c-467d-a6f5-4a32ddd96dbb", 19 | "companyName": "AxonIQ", 20 | "companyValue": "1337", 21 | "amountOfShares": "42" 22 | } 23 | 24 | ### 25 | 26 | POST https://esrefarch-demo-trader-app.cfapps.io/command/CreatePortfolioCommand 27 | Content-Type: application/json 28 | 29 | { 30 | "portfolioId":"abae5981-9eea-4b0a-b2cd-54002a837409", 31 | "userId":"1bae0365-949c-467d-a6f5-4a32ddd96dbb" 32 | } 33 | 34 | ### 35 | 36 | GET http://localhost:8080/status 37 | Accept: application/json 38 | 39 | ### 40 | 41 | GET http://localhost:8761/ 42 | Accept: application/json 43 | 44 | ### 45 | -------------------------------------------------------------------------------- /images/AxonTrader-UI-001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pivotalsoftware/ESarch/c6e2f272b3cb99b414bb1e1b0456c9d0f3efe359/images/AxonTrader-UI-001.png -------------------------------------------------------------------------------- /images/CQRS+EventSourcing-on-CloudFoundry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pivotalsoftware/ESarch/c6e2f272b3cb99b414bb1e1b0456c9d0f3efe359/images/CQRS+EventSourcing-on-CloudFoundry.jpg -------------------------------------------------------------------------------- /pave.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # Use this script to pave your PWS instance with the correct services 3 | 4 | set -eu 5 | 6 | if [ -z ${1+x} ] && [ -z ${2+x} ] && [ -z ${3+x} ] && [ -z ${4+x} ]; then 7 | echo "Usage: USERNAME PASSWORD ORG SPACE" 8 | exit 1 9 | fi 10 | 11 | export CF='command -v cf' 12 | if [[ $? != 0 ]]; then 13 | echo "I don't think the CF command line client (cf-cli) is installed." 14 | echo "Please see the instructions here: https://docs.cloudfoundry.org/cf-cli/install-go-cli.html" 15 | exit 1 16 | else 17 | echo $'\nYou have the CF command line client already; that\'s awesome!\n' 18 | fi 19 | 20 | export API="https://api.run.pivotal.io" 21 | echo "API: ${API}" 22 | 23 | if [ -z ${1+x} ]; then 24 | echo "I need your PWS username (email address) to continue." 25 | exit 1 26 | else 27 | echo "Username: $1" 28 | fi 29 | 30 | if [ -z ${2+x} ]; then 31 | echo "I need your PWS password to continue." 32 | exit 1 33 | else 34 | echo "Password is {SECRET}." 35 | fi 36 | 37 | if [ -z ${3+x} ]; then 38 | echo "I need your PWS ORG name to continue." 39 | exit 1 40 | else 41 | echo "Org: $3" 42 | fi 43 | 44 | if [ -z ${4+x} ]; then 45 | echo "I need your PWS SPACE name to continue." 46 | exit 1 47 | else 48 | echo "Space: $4" 49 | fi 50 | 51 | echo $'Logging in to PWS using the details provided...\n' 52 | 53 | cf login -a ${API} -u $1 -p $2 -o $3 -s $4 54 | 55 | if [[ $? != 0 ]]; then 56 | echo "Abort. There was a problem logging in to PWS with the details given. Please resolve the problem and try again." 57 | exit 1 58 | else 59 | echo $'\nWe\'re logged in. Super!\n' 60 | fi 61 | 62 | echo $'Now we\'ll create some services using the Cloud Foundry marketplace [#AwesomePCF #NoOps :D ]...\n' 63 | 64 | cf create-service cleardb spark enginedb 65 | cf create-service cleardb spark appdb 66 | cf create-service cloudamqp lemur rabbit 67 | cf create-service p-service-registry trial registry 68 | cf create-service p-config-server trial config -c config-server-setup.json 69 | 70 | echo $'\nNow we need to wait a few minutes until all five 71 | services show \"create succeeded\" as their status.' 72 | echo "Press [CTRL+C] any time to quit (the services will still be created)." 73 | read -p "Press the [Enter] key to watch while the services get created..." 74 | 75 | watch -n 1 "cf services" 76 | 77 | echo $'\nWe\'re done. You can now deploy the Axom Trader applications to Pivotal Web Services.' -------------------------------------------------------------------------------- /teardown.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | Use this script to unpave your PWS space 3 | 4 | set -eu 5 | 6 | if [ -z ${1+x} ] && [ -z ${2+x} ] && [ -z ${3+x} ] && [ -z ${4+x} ]; then 7 | echo "Usage: USERNAME PASSWORD ORG SPACE" 8 | exit 1 9 | fi 10 | 11 | export CF='command -v cf' 12 | if [[ $? != 0 ]]; then 13 | echo "I don't think the CF command line client (cf-cli) is installed." 14 | echo "Please see the instructions here: https://docs.cloudfoundry.org/cf-cli/install-go-cli.html" 15 | exit 1 16 | else 17 | echo $'\nYou have the CF command line client already; that\'s awesome!\n' 18 | fi 19 | 20 | export API="https://api.run.pivotal.io" 21 | echo "API: ${API}" 22 | 23 | if [ -z ${1+x} ]; then 24 | echo "I need your PWS username (email address) to continue." 25 | exit 1 26 | else 27 | echo "Username: $1" 28 | fi 29 | 30 | if [ -z ${2+x} ]; then 31 | echo "I need your PWS password to continue." 32 | exit 1 33 | else 34 | echo "Password is {SECRET}." 35 | fi 36 | 37 | if [ -z ${3+x} ]; then 38 | echo "I need your PWS ORG name to continue." 39 | exit 1 40 | else 41 | echo "Org: $3" 42 | fi 43 | 44 | if [ -z ${4+x} ]; then 45 | echo "I need your PWS SPACE name to continue." 46 | exit 1 47 | else 48 | echo "Space: $4" 49 | fi 50 | 51 | echo $'Logging in to PWS using the details provided...\n' 52 | 53 | cf login -a ${API} -u $1 -p $2 -o $3 -s $4 54 | 55 | if [[ $? != 0 ]]; then 56 | echo "Abort. There was a problem logging in to PWS with the details given. Please resolve the problem and try again." 57 | exit 1 58 | else 59 | echo $'\nWe\'re logged in. Super!\n' 60 | fi 61 | 62 | 63 | cf stop esrefarch-demo-trader-app 64 | cf stop esrefarch-demo-trader-ui 65 | cf stop esrefarch-demo-trading-engine 66 | 67 | cf us esrefarch-demo-trader-app registry 68 | cf us esrefarch-demo-trading-engine registry 69 | cf us esrefarch-demo-trader-app config 70 | cf us esrefarch-demo-trading-engine config 71 | cf us esrefarch-demo-trader-ui config 72 | cf us esrefarch-demo-trader-app rabbit 73 | cf us esrefarch-demo-trading-engine rabbit 74 | cf us esrefarch-demo-trading-engine enginedb 75 | cf us esrefarch-demo-trader-app appdb 76 | 77 | cf ds rabbit -f 78 | cf ds config -f 79 | cf ds registry -f 80 | cf ds appdb -f 81 | cf ds enginedb -f 82 | 83 | cf delete esrefarch-demo-trader-app -f 84 | cf delete esrefarch-demo-trader-ui -f 85 | cf delete esrefarch-demo-trading-engine -f 86 | 87 | exit 0 -------------------------------------------------------------------------------- /trader-app-ui/.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /node_modules 3 | /build 4 | /src/registerServiceWorker.js -------------------------------------------------------------------------------- /trader-app-ui/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "jasmine": true 5 | }, 6 | "extends": ["airbnb"] 7 | } 8 | -------------------------------------------------------------------------------- /trader-app-ui/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # dependencies 3 | /node_modules 4 | 5 | # testing 6 | /coverage 7 | 8 | # production 9 | /build 10 | 11 | # misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | 22 | # dotenv environment variables file 23 | .env -------------------------------------------------------------------------------- /trader-app-ui/README.md: -------------------------------------------------------------------------------- 1 | # Axon Trader Application UI Built on ReactJS and Redux 2 | 3 | ## Development 4 | **Prerequisites:** `node` v8 and `npm` v5 5 | 6 | - Add `.env` file to the root directory and include the following environmental variables: 7 | - `REACT_APP_API_ROOT="http://localhost:8081"` (replace the text with your API url) 8 | 9 | ### Run the application: 10 | 11 | - install dependencies - `npm install` 12 | - run the app (dev server) - `npm start` and visit http://localhost:3000/ to view the application 13 | 14 | 15 | Run tests - `npm run test` 16 | 17 | Production build - `npm run build` 18 | 19 | *This application is configured using [create-react-app](https://github.com/facebook/create-react-app "create-react-app github repo"). Please read it's documentation for further details of the application structure* 20 | -------------------------------------------------------------------------------- /trader-app-ui/Staticfile: -------------------------------------------------------------------------------- 1 | directory: visible 2 | pushstate: enabled -------------------------------------------------------------------------------- /trader-app-ui/manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: esrefarch-demo-trader-ui 4 | timeout: 120 5 | instances: 1 6 | buildpacks: 7 | - staticfile_buildpack 8 | path: build 9 | env: 10 | TRUST_CERTS: api.run.pivotal.io -------------------------------------------------------------------------------- /trader-app-ui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "trader-app-ui", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "history": "^4.7.2", 7 | "react": "^16.4.2", 8 | "react-bootstrap": "^0.32.3", 9 | "react-dom": "^16.4.2", 10 | "react-redux": "^5.0.7", 11 | "react-router-dom": "^4.3.1", 12 | "react-router-redux": "^5.0.0-alpha.9", 13 | "react-scripts": "1.1.5", 14 | "redux": "^4.0.0", 15 | "redux-thunk": "^2.3.0" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test --env=jsdom", 21 | "eject": "react-scripts eject" 22 | }, 23 | "devDependencies": { 24 | "dotenv": "^6.0.0", 25 | "eslint": "^5.6.1", 26 | "eslint-config-airbnb": "^17.1.0", 27 | "eslint-plugin-import": "^2.14.0", 28 | "eslint-plugin-jsx-a11y": "^6.1.1", 29 | "eslint-plugin-react": "^7.11.1", 30 | "fetch-mock": "^7.0.2", 31 | "node-fetch": "^2.2.0", 32 | "redux-mock-store": "^1.5.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /trader-app-ui/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pivotalsoftware/ESarch/c6e2f272b3cb99b414bb1e1b0456c9d0f3efe359/trader-app-ui/public/favicon.ico -------------------------------------------------------------------------------- /trader-app-ui/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 25 | Axon Trader 26 | 27 | 28 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /trader-app-ui/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /trader-app-ui/src/actions/home.js: -------------------------------------------------------------------------------- 1 | import { 2 | FETCH_USERS_REQUEST, 3 | FETCH_USERS_SUCCESS, 4 | FETCH_USERS_FAILURE, 5 | SET_IMPERSONATED_USER, 6 | LOGOUT_USER, 7 | } from '../constants/homeActions'; 8 | import { status, json } from '../utils/fetch'; 9 | import { ApiConfig } from '../utils/config'; 10 | 11 | const API_ROOT = ApiConfig(); 12 | 13 | export const fetchUsersRequest = () => ( 14 | { 15 | type: FETCH_USERS_REQUEST, 16 | payload: { 17 | isFetching: true, 18 | }, 19 | } 20 | ); 21 | 22 | export const fetchUsersSuccess = data => ( 23 | { 24 | type: FETCH_USERS_SUCCESS, 25 | payload: { 26 | isFetching: false, 27 | data, 28 | }, 29 | } 30 | ); 31 | 32 | export const fetchUsersFailure = error => ( 33 | { 34 | type: FETCH_USERS_FAILURE, 35 | payload: { 36 | isFetching: false, 37 | error, 38 | }, 39 | } 40 | ); 41 | 42 | export const validImpersonatedUser = (userList, currentImpersonatedUserId) => { 43 | for (const user of userList) { 44 | if (user.identifier === currentImpersonatedUserId) { 45 | return true; 46 | } 47 | } 48 | return false; 49 | }; 50 | 51 | export const getUsers = () => (dispatch, getState) => { 52 | const appState = getState(); 53 | 54 | dispatch(fetchUsersRequest()); 55 | 56 | const options = { 57 | method: 'GET', 58 | headers: { 59 | 'Content-Type': 'application/json', 60 | }, 61 | }; 62 | 63 | return fetch(`${API_ROOT}/query/user`, options) 64 | .then(status) 65 | .then(json) 66 | .then((data) => { 67 | // got a successfull response from the server 68 | 69 | // verify the currently impersonated user is valid 70 | if (appState.home.impersonatedUser && appState.home.impersonatedUser.identifier) { 71 | const impersonatedUserId = appState.home.impersonatedUser.identifier; 72 | if (!validImpersonatedUser(data, impersonatedUserId)) { 73 | console.log('invalid impersonated user. logging out..'); 74 | dispatch({ 75 | type: LOGOUT_USER, 76 | }); 77 | } 78 | } 79 | dispatch(fetchUsersSuccess(data)); 80 | }) 81 | .catch((error) => { 82 | // bad response 83 | dispatch(fetchUsersFailure(error)); 84 | }); 85 | }; 86 | 87 | export function setImpersonatedUser(user) { 88 | // todo redirect to logout page 89 | return { 90 | type: SET_IMPERSONATED_USER, 91 | payload: { 92 | impersonatedUser: user, 93 | }, 94 | }; 95 | } 96 | -------------------------------------------------------------------------------- /trader-app-ui/src/actions/portfolio.test.js: -------------------------------------------------------------------------------- 1 | import { 2 | fetchPortfolioRequest, 3 | fetchPortfolioSuccess, 4 | fetchPortfolioFailure, 5 | } from './portfolio'; 6 | import * as actions from '../constants/portfolioActions'; 7 | 8 | describe('portfolioActions: fetch portfolio request', () => { 9 | it('should create an action to request a portfolio', () => { 10 | const expectedAction = { 11 | type: actions.FETCH_PORTFOLIO_REQUEST, 12 | payload: { 13 | isFetching: true, 14 | }, 15 | }; 16 | expect(fetchPortfolioRequest()).toEqual(expectedAction); 17 | }); 18 | }); 19 | 20 | describe('portfolioActions: fetch portfolio success', () => { 21 | it('should create an action to handle when a portfolio is fetched successfully', () => { 22 | const response = [ 23 | { 24 | identifier: 'safh43fh1234', 25 | userId: '456456757dfgd43', 26 | amountOfMoney: '2000', 27 | }, 28 | ]; 29 | const expectedAction = { 30 | type: actions.FETCH_PORTFOLIO_SUCCESS, 31 | payload: { 32 | isFetching: false, 33 | data: response, 34 | }, 35 | }; 36 | expect(fetchPortfolioSuccess(response)).toEqual(expectedAction); 37 | }); 38 | }); 39 | 40 | describe('portfolioActions: fetch portfolio failure', () => { 41 | it('should create an action to handle errors when fetching a portfolio', () => { 42 | const error = { 43 | message: 'Not Found', 44 | status: 404, 45 | }; 46 | const expectedAction = { 47 | type: actions.FETCH_PORTFOLIO_FAILURE, 48 | payload: { 49 | isFetching: false, 50 | error, 51 | }, 52 | }; 53 | expect(fetchPortfolioFailure(error)).toEqual(expectedAction); 54 | }); 55 | }); 56 | -------------------------------------------------------------------------------- /trader-app-ui/src/app.scss: -------------------------------------------------------------------------------- 1 | .appBody { 2 | margin: 100px; 3 | background: white; 4 | overflow: auto; 5 | } -------------------------------------------------------------------------------- /trader-app-ui/src/assets/home-background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pivotalsoftware/ESarch/c6e2f272b3cb99b414bb1e1b0456c9d0f3efe359/trader-app-ui/src/assets/home-background.png -------------------------------------------------------------------------------- /trader-app-ui/src/assets/home-foreground-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pivotalsoftware/ESarch/c6e2f272b3cb99b414bb1e1b0456c9d0f3efe359/trader-app-ui/src/assets/home-foreground-image.png -------------------------------------------------------------------------------- /trader-app-ui/src/components/Company/DataTable.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const dataTable = props => ( 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | { 14 | props.data && props.data.map((item, index) => { 15 | const className = index % 2 === 0 ? 'list-row-white' : 'list-row-gray'; 16 | const price = item.itemPrice !== undefined ? item.itemPrice : item.tradePrice; 17 | 18 | return ( 19 | 20 | 21 | 22 | 23 | ); 24 | }) 25 | } 26 | 27 |
CountPrice
{item.itemsRemaining.toLocaleString('en')}{price.toLocaleString('en', { style: 'currency', currency: 'USD' })}
28 |
29 | ); 30 | 31 | export default dataTable; 32 | -------------------------------------------------------------------------------- /trader-app-ui/src/components/Dashboard/Portfolio.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Portfolio = (props) => { 4 | const moneyFormatOptions = { style: 'currency', currency: 'USD' }; 5 | 6 | return ( 7 |
8 |

{props.title}

9 |
10 |

{props.description}

11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
Money 
Available{(props.moneyAvailable).toLocaleString('en', moneyFormatOptions)}
Reserved{(props.reserved).toLocaleString('en', moneyFormatOptions)}
29 |
30 | ); 31 | }; 32 | 33 | export default Portfolio; 34 | -------------------------------------------------------------------------------- /trader-app-ui/src/components/Dashboard/TradeItemsContainer.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const TradeItemsContainer = (props) => { 4 | let className = 'container-trade-items'; 5 | 6 | if (props.className) { 7 | className += ` ${props.className}`; 8 | } 9 | 10 | return ( 11 |
12 | {props.children} 13 |
14 | ); 15 | }; 16 | 17 | export default TradeItemsContainer; 18 | -------------------------------------------------------------------------------- /trader-app-ui/src/components/Dashboard/TradeItemsTable.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const TradeItemsTable = ({ items, tableName }) => ( 4 |
5 |

{tableName}

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | { 15 | Object.keys(items).map((key, index) => ( 16 | 17 | 21 | 25 | 26 | )) 27 | } 28 | 29 |
NameAmount
18 | {' '} 19 | {items[key].companyName} 20 | 22 | {' '} 23 | {items[key].amount.toLocaleString('en')} 24 |
30 |
31 | ); 32 | 33 | export default TradeItemsTable; 34 | -------------------------------------------------------------------------------- /trader-app-ui/src/components/Dashboard/Transactions.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import formatTransactionState from '../../utils/transactions'; 3 | 4 | const Transactions = ({ transactions, title, description }) => ( 5 |
6 |

{title}

7 |
8 |

{description}

9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | { 24 | transactions.map(transaction => ( 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | )) 35 | } 36 | 37 |
NameType# SharesPriceTotal PriceShares ExecutedState
{transaction.companyName}{transaction.type}{transaction.amountOfItems.toLocaleString('en')}{(transaction.pricePerItem).toLocaleString('en', { style: 'currency', currency: 'USD' })}{(transaction.amountOfItems * transaction.pricePerItem).toLocaleString('en', { style: 'currency', currency: 'USD' })}{transaction.amountOfExecutedItems.toLocaleString('en')}{formatTransactionState(transaction.state)}
38 |
39 |
40 | ); 41 | 42 | export default Transactions; 43 | -------------------------------------------------------------------------------- /trader-app-ui/src/components/Dashboard/styles.css: -------------------------------------------------------------------------------- 1 | .container-trade-items { 2 | padding: 40px 76px; 3 | background-color: #00ACE7; 4 | } 5 | 6 | .dashboard-header { 7 | font-family: Roboto, Arial, Helvetica, sans-serif; 8 | font-size: 64px; 9 | font-weight: bold; 10 | font-style: normal; 11 | font-stretch: normal; 12 | line-height: normal; 13 | letter-spacing: 0px; 14 | color: #01ade5; 15 | } 16 | 17 | .dashboard-subheader { 18 | font-family: Montserrat, Arial, Helvetica, sans-serif; 19 | font-size: 24px; 20 | font-weight: 600; 21 | font-style: normal; 22 | font-stretch: normal; 23 | line-height: 1.38; 24 | letter-spacing: normal; 25 | color: #212121; 26 | } 27 | 28 | .portfolio-title { 29 | font-family: Roboto, Arial, Helvetica, sans-serif; 30 | font-size: 24px; 31 | font-weight: bold; 32 | font-style: normal; 33 | font-stretch: normal; 34 | line-height: normal; 35 | letter-spacing: 0px; 36 | color: #00253e; 37 | } 38 | 39 | .portfolio-subtitle { 40 | font-family: Montserrat, Arial, Helvetica, sans-serif; 41 | font-size: 16px; 42 | font-weight: normal; 43 | font-style: normal; 44 | font-stretch: normal; 45 | line-height: 1.63; 46 | letter-spacing: normal; 47 | color: #212121; 48 | } 49 | 50 | .trade-items-title { 51 | font-family: Roboto, Arial, Helvetica, sans-serif; 52 | font-size: 24px; 53 | font-weight: bold; 54 | font-style: normal; 55 | font-stretch: normal; 56 | line-height: normal; 57 | letter-spacing: 0px; 58 | color: #ffffff; 59 | } 60 | 61 | .dashboard-divider { 62 | background-color: #212121; 63 | height: 3px; 64 | width: 300px; 65 | margin-bottom: 18px; 66 | } 67 | 68 | .dashboard-table { 69 | border-collapse: collapse; 70 | font-family: Montserrat, Arial, Helvetica, sans-serif; 71 | font-size: 16px; 72 | font-style: normal; 73 | font-stretch: normal; 74 | line-height: normal; 75 | letter-spacing: 0px; 76 | color: rgba(0, 37, 62, 0.95); 77 | } 78 | 79 | .dashboard-table th { 80 | background-color: #f6f6f6; 81 | font-weight: 600; 82 | } 83 | 84 | .dashboard-trades-table { 85 | border-collapse: collapse; 86 | font-family: Montserrat; 87 | font-size: 16px; 88 | font-style: normal; 89 | font-stretch: normal; 90 | line-height: normal; 91 | letter-spacing: 0px; 92 | color: #ffffff; 93 | opacity: 0.8; 94 | } 95 | 96 | .dashboard-trades-table th { 97 | background-color: #7FD5F0; 98 | font-weight: 600; 99 | } -------------------------------------------------------------------------------- /trader-app-ui/src/components/Home/Banner.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | const Banner = () => ( 5 |
6 |

The Trader

7 |

8 | Welcome to the Axon Trader application. 9 | Axon is the leading open source CQRS and Event Sourcing framework for 10 |   11 | Spring Boot 12 | . 13 |  Axon Trader showcases various 14 | {' '} 15 | Axon Framework 16 |  capabilities and it is built for 17 | {' '} 18 | Pivotal Application Service 19 |  (provided by 20 | {' '} 21 | Pivotal Web Services 22 | ). 23 | For more information see 24 | {' '} 25 | code 26 | {' '} 27 | and 28 | {' '} 29 | wiki 30 | {' '} 31 | on GitHub. 32 |

33 |

34 | Choose a user to impersonate below, then go to your account dashboard. 35 |

36 | DASHBOARD 37 |
38 | ); 39 | 40 | export default Banner; 41 | -------------------------------------------------------------------------------- /trader-app-ui/src/components/Home/CredentialsTable.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const CredentialsTable = ({ credentials, onSetImpersonatedUser }) => 4 |
5 |

Available Credentials

6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | {credentials && 17 | credentials.sort((a, b) => { 18 | return a.userName > b.userName; 19 | }).map(user => { 20 | return ( 21 | 22 | 23 | 24 | 29 | 30 | ); 31 | }) 32 | } 33 | 34 |
UsernameFull Name
{user.userName}{user.fullName} 25 | 28 |
35 |
36 |
; 37 | 38 | export default CredentialsTable; 39 | -------------------------------------------------------------------------------- /trader-app-ui/src/components/Home/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Header = () => ( 4 |
5 |

Welcome

6 |

Have fun playing with the trader

7 |
8 |

9 | There are a few things implemented. 10 | You can choose the company to trade stock shares for. 11 | Before you can use them you need to login. 12 |

13 |
14 | ); 15 | 16 | export default Header; 17 | -------------------------------------------------------------------------------- /trader-app-ui/src/components/Home/SideBar.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SideBarItem from './SideBarItem'; 3 | 4 | const data = [ 5 | { 6 | header: 'Check the stocks', 7 | description: 'If you have logged in, you can go to the companies', 8 | linkTo: '/orderbooks', 9 | linkName: 'To the stocks', 10 | }, 11 | ]; 12 | 13 | const SideBar = () => ( 14 |
15 | { 16 | data.map(item => ( 17 | 24 | )) 25 | } 26 |
27 | ); 28 | 29 | export default SideBar; 30 | -------------------------------------------------------------------------------- /trader-app-ui/src/components/Home/SideBarItem.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | const SideBarItem = props => ( 5 |
6 |

{props.header}

7 |

{props.description}

8 | {props.linkName} 9 |
10 | ); 11 | 12 | export default SideBarItem; 13 | -------------------------------------------------------------------------------- /trader-app-ui/src/components/Home/credentials.js: -------------------------------------------------------------------------------- 1 | const credentials = [ 2 | { userName: 'buyer1', password: 'buyer1' }, 3 | { userName: 'buyer2', password: 'buyer2' }, 4 | { userName: 'buyer3', password: 'buyer3' }, 5 | { userName: 'buyer4', password: 'buyer4' }, 6 | { userName: 'buyer5', password: 'buyer5' }, 7 | { userName: 'buyer6', password: 'buyer6' }, 8 | ]; 9 | 10 | export default credentials; 11 | -------------------------------------------------------------------------------- /trader-app-ui/src/components/Home/img/fondo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pivotalsoftware/ESarch/c6e2f272b3cb99b414bb1e1b0456c9d0f3efe359/trader-app-ui/src/components/Home/img/fondo.jpg -------------------------------------------------------------------------------- /trader-app-ui/src/components/Home/img/fondo@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pivotalsoftware/ESarch/c6e2f272b3cb99b414bb1e1b0456c9d0f3efe359/trader-app-ui/src/components/Home/img/fondo@2x.jpg -------------------------------------------------------------------------------- /trader-app-ui/src/components/Home/img/fondo@3x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pivotalsoftware/ESarch/c6e2f272b3cb99b414bb1e1b0456c9d0f3efe359/trader-app-ui/src/components/Home/img/fondo@3x.jpg -------------------------------------------------------------------------------- /trader-app-ui/src/components/Home/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import CredentialsTable from './CredentialsTable'; 3 | import './styles.css'; 4 | import { bindActionCreators } from 'redux' 5 | import { connect } from 'react-redux' 6 | import SideBar from './SideBar'; 7 | import Banner from './Banner'; 8 | import Header from './Header'; 9 | import * as homeActionCreators from '../../actions/home' 10 | 11 | class Home extends Component { 12 | 13 | componentDidMount() { 14 | this.props.homeActions.getUsers(); 15 | } 16 | 17 | render() { 18 | const { users } = this.props; 19 | 20 | return ( 21 |
22 |
23 |
24 |
25 | axon banner 28 |
29 |
30 | 31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | 39 |
40 |
41 |
42 |
43 | 44 |
45 |
46 |
47 |
48 | ) 49 | } 50 | 51 | setImpersonatedUser = (user) => { 52 | this.props.homeActions.setImpersonatedUser(user); 53 | this.props.history.push('/dashboard'); 54 | } 55 | } 56 | 57 | const mapDispatchToProps = (dispatch) => { 58 | return { 59 | homeActions: bindActionCreators(homeActionCreators, dispatch) 60 | } 61 | } 62 | 63 | const mapStateToProps = state => { 64 | return { 65 | users: state.home.users 66 | } 67 | } 68 | 69 | export default connect(mapStateToProps, mapDispatchToProps)(Home); -------------------------------------------------------------------------------- /trader-app-ui/src/components/Loader/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './styles.css'; 3 | 4 | const Loader = (props) => { 5 | let className = 'axon-loader text-center'; 6 | 7 | if (props.className) { 8 | className += ` ${props.className}`; 9 | } 10 | 11 | return ( 12 |
13 | ); 14 | }; 15 | 16 | export default Loader; 17 | -------------------------------------------------------------------------------- /trader-app-ui/src/components/Loader/styles.css: -------------------------------------------------------------------------------- 1 | .axon-loader { 2 | border: 16px solid transparent; 3 | border-radius: 50%; 4 | border-top: 16px solid #01ade5; 5 | width: 120px; 6 | height: 120px; 7 | -webkit-animation: spin 1s linear infinite; /* Safari */ 8 | animation: spin 1s linear infinite; 9 | } 10 | 11 | /* Safari */ 12 | @-webkit-keyframes spin { 13 | 0% { -webkit-transform: rotate(0deg); } 14 | 100% { -webkit-transform: rotate(360deg); } 15 | } 16 | 17 | @keyframes spin { 18 | 0% { transform: rotate(0deg); } 19 | 100% { transform: rotate(360deg); } 20 | } 21 | -------------------------------------------------------------------------------- /trader-app-ui/src/constants.js: -------------------------------------------------------------------------------- 1 | export const Constants = { 2 | DEFAULT: 'Default', 3 | RESERVED: 'Reserved', 4 | }; 5 | -------------------------------------------------------------------------------- /trader-app-ui/src/constants/companyActions.js: -------------------------------------------------------------------------------- 1 | export const FETCH_COMPANY_REQUEST = 'FETCH_COMPANY_REQUEST'; 2 | export const FETCH_COMPANY_SUCCESS = 'FETCH_COMPANY_SUCCESS'; 3 | export const FETCH_COMPANY_FAILURE = 'FETCH_COMPANY_FAILURE'; 4 | export const FETCH_COMPANY_LIST_REQUEST = 'FETCH_COMPANY_LIST_REQUEST'; 5 | export const FETCH_COMPANY_LIST_SUCCESS = 'FETCH_COMPANY_LIST_SUCCESS'; 6 | export const FETCH_COMPANY_LIST_FAILURE = 'FETCH_COMPANY_LIST_FAILURE'; 7 | export const FETCH_ORDERBOOKS_BY_COMPANYID_REQUEST = 'FETCH_ORDERBOOKS_BY_COMPANYID_REQUEST'; 8 | export const FETCH_ORDERBOOKS_BY_COMPANYID_SUCCESS = 'FETCH_ORDERBOOKS_BY_COMPANYID_SUCCESS'; 9 | export const FETCH_ORDERBOOKS_BY_COMPANYID_FAILURE = 'FETCH_ORDERBOOKS_BY_COMPANYID_FAILURE'; 10 | export const PLACE_BUY_ORDER_REQUEST = 'PLACE_BUY_ORDER_REQUEST'; 11 | export const PLACE_BUY_ORDER_SUCCESS = 'PLACE_BUY_ORDER_SUCCESS'; 12 | export const PLACE_BUY_ORDER_FAILURE = 'PLACE_BUY_ORDER_FAILURE'; 13 | export const PLACE_SELL_ORDER_REQUEST = 'PLACE_SELL_ORDER_REQUEST'; 14 | export const PLACE_SELL_ORDER_SUCCESS = 'PLACE_SELL_ORDER_SUCCESS'; 15 | export const PLACE_SELL_ORDER_FAILURE = 'PLACE_SELL_ORDER_FAILURE'; 16 | export const SET_SSE_ORDERBOOK_DATA = 'SET_SSE_ORDERBOOK_DATA'; 17 | -------------------------------------------------------------------------------- /trader-app-ui/src/constants/homeActions.js: -------------------------------------------------------------------------------- 1 | export const FETCH_USERS_REQUEST = 'FETCH_USERS_REQUEST'; 2 | export const FETCH_USERS_SUCCESS = 'FETCH_USERS_SUCCESS'; 3 | export const FETCH_USERS_FAILURE = 'FETCH_USERS_FAILURE'; 4 | export const SET_IMPERSONATED_USER = 'SET_IMPERSONATED_USER'; 5 | export const LOGOUT_USER = 'LOGOUT_USER'; 6 | -------------------------------------------------------------------------------- /trader-app-ui/src/constants/portfolioActions.js: -------------------------------------------------------------------------------- 1 | export const FETCH_PORTFOLIO_REQUEST = 'FETCH_PORTFOLIO_REQUEST'; 2 | export const FETCH_PORTFOLIO_SUCCESS = 'FETCH_PORTFOLIO_SUCCESS'; 3 | export const FETCH_PORTFOLIO_FAILURE = 'FETCH_PORTFOLIO_FAILURE'; 4 | export const FETCH_TRANSACTIONS_BY_PORTFOLIOID_REQUEST = 'FETCH_TRANSACTIONS_BY_PORTFOLIOID_REQUEST'; 5 | export const FETCH_TRANSACTIONS_BY_PORTFOLIOID_SUCCESS = 'FETCH_TRANSACTIONS_BY_PORTFOLIOID_SUCCESS'; 6 | export const FETCH_TRANSACTIONS_BY_PORTFOLIOID_FAILURE = 'FETCH_TRANSACTIONS_BY_PORTFOLIOID_FAILURE'; 7 | -------------------------------------------------------------------------------- /trader-app-ui/src/containers/App/App.test.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './index'; 4 | 5 | it('renders without crashing', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | }); 10 | -------------------------------------------------------------------------------- /trader-app-ui/src/containers/App/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { createStore, applyMiddleware, compose } from 'redux'; 4 | import thunk from 'redux-thunk'; 5 | import createHistory from 'history/createBrowserHistory'; 6 | import { ConnectedRouter, routerMiddleware } from 'react-router-redux'; 7 | import { Route, Switch } from 'react-router-dom'; 8 | import NavbarContainer from '../NavbarContainer'; 9 | import LoginContainer from '../LoginContainer'; 10 | import Home from '../../components/Home'; 11 | import DashboardContainer from '../DashboardContainer'; 12 | import Companies from '../CompanyListContainer/CompanyListContainer'; 13 | import SecureRoute from '../SecureRoute'; 14 | import CompanyContainer from '../CompanyContainer'; 15 | import rootReducer from '../../reducers'; 16 | import { scrollToAnimated } from '../../utils/animation'; 17 | import { loadState, saveState } from './sessionStorage'; 18 | 19 | const history = createHistory(); 20 | const middleware = routerMiddleware(history); 21 | const persistedState = loadState(); 22 | 23 | const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; 24 | 25 | history.listen((location) => { 26 | if (location.pathname === '/' && location.hash === '#credentials') { 27 | setTimeout(() => { 28 | scrollToAnimated(document.body.scrollHeight, 300); 29 | }, 50); 30 | } else { 31 | window.scrollTo(0, 0); 32 | } 33 | }); 34 | 35 | const store = createStore( 36 | rootReducer, 37 | persistedState, 38 | composeEnhancers(applyMiddleware(thunk, middleware)), 39 | ); 40 | 41 | store.subscribe(() => { 42 | saveState(store.getState()); 43 | }); 44 | 45 | export default class App extends Component { 46 | render() { 47 | return ( 48 | 49 | 50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |
62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /trader-app-ui/src/containers/App/sessionStorage.js: -------------------------------------------------------------------------------- 1 | export const loadState = () => { 2 | try { 3 | const serializedHomeState = sessionStorage.getItem('axonHomeState'); 4 | const serializedPortfolioState = sessionStorage.getItem('axonPortfolioState'); 5 | const initialState = {}; 6 | 7 | if (serializedHomeState == null && serializedPortfolioState == null) { 8 | return undefined; 9 | } 10 | 11 | if (serializedHomeState) { 12 | initialState.home = JSON.parse(serializedHomeState); 13 | } 14 | 15 | if (serializedPortfolioState) { 16 | initialState.portfolio = JSON.parse(serializedPortfolioState); 17 | } 18 | return initialState; 19 | } catch (err) { 20 | return undefined; 21 | } 22 | }; 23 | 24 | export const saveState = (state) => { 25 | try { 26 | // save state.home (authentication info) in sessionStorage 27 | const serializedHomeState = JSON.stringify(state.home); 28 | sessionStorage.setItem('axonHomeState', serializedHomeState); 29 | 30 | const serializedPortfolioState = JSON.stringify(state.portfolio); 31 | sessionStorage.setItem('axonPortfolioState', serializedPortfolioState); 32 | } catch (err) { 33 | // ignore write errors 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /trader-app-ui/src/containers/CompanyListContainer/CompanyListContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { bindActionCreators } from 'redux'; 3 | import { connect } from 'react-redux'; 4 | import CompanyList from '../../components/Company/CompanyList'; 5 | import * as companyActionCreators from '../../actions/company'; 6 | import './styles.css'; 7 | 8 | class CompanyListContainer extends Component { 9 | componentDidMount() { 10 | this.props.companyActions.fetchCompanyList(); 11 | } 12 | 13 | render() { 14 | return ( 15 |
16 |

All stock items

17 |

Choose the stock to start trading with

18 |
19 |

You can sort the table by clicking on the headers

20 |
21 | 22 |
23 |
24 | ); 25 | } 26 | } 27 | 28 | const mapDispatchToProps = dispatch => ({ 29 | companyActions: bindActionCreators(companyActionCreators, dispatch), 30 | }); 31 | 32 | const mapStateToProps = state => ({ 33 | companies: state.companies, 34 | }); 35 | 36 | export default connect(mapStateToProps, mapDispatchToProps)(CompanyListContainer); 37 | -------------------------------------------------------------------------------- /trader-app-ui/src/containers/CompanyListContainer/styles.css: -------------------------------------------------------------------------------- 1 | .company-list-container { 2 | padding-top: 20px; 3 | } 4 | 5 | .company-list-title { 6 | margin-top: 66px; 7 | font-size: 50px; 8 | color: #00253E; 9 | font-family: Roboto; 10 | font-weight: bold; 11 | font-style: normal; 12 | } 13 | 14 | .company-list-subtitle { 15 | margin-top: 22px; 16 | font-family: Montserrat; 17 | font-size: 20px; 18 | font-weight: 600; 19 | line-height: 1.08; 20 | color: #01ADE5; 21 | } 22 | 23 | .small-divider { 24 | background-color: #212121; 25 | height: 3px; 26 | width: 22px; 27 | margin-bottom: 15px; 28 | } 29 | 30 | .company-list-sort-text { 31 | font-family: Montserrat; 32 | font-size: 14px; 33 | font-weight: normal; 34 | font-style: normal; 35 | font-stretch: normal; 36 | line-height: 1.63; 37 | letter-spacing: normal; 38 | color: #00253e; 39 | margin-bottom: 35px; 40 | } -------------------------------------------------------------------------------- /trader-app-ui/src/containers/DashboardContainer.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { bindActionCreators } from 'redux'; 3 | import { connect } from 'react-redux'; 4 | import Dashboard from '../components/Dashboard'; 5 | import * as portfolioActionCreators from '../actions/portfolio'; 6 | 7 | class DashboardContainer extends Component { 8 | componentDidMount() { 9 | this.props.portfolioActions.getPortfolioAndItsTransactions(this.props.impersonatedUser.userId); 10 | } 11 | 12 | render() { 13 | const { portfolio } = this.props; 14 | return ( 15 |
16 | 17 |
18 | ); 19 | } 20 | } 21 | 22 | const mapDispatchToProps = dispatch => ({ 23 | portfolioActions: bindActionCreators(portfolioActionCreators, dispatch), 24 | }); 25 | 26 | const mapStateToProps = state => ({ 27 | portfolio: state.portfolio, 28 | impersonatedUser: state.home.impersonatedUser, 29 | }); 30 | 31 | export default connect(mapStateToProps, mapDispatchToProps)(DashboardContainer); 32 | -------------------------------------------------------------------------------- /trader-app-ui/src/containers/LoginContainer/index.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Redirect, Link } from 'react-router-dom'; 4 | import './styles.css'; 5 | 6 | class LoginContainer extends Component { 7 | render() { 8 | const { impersonatedUser } = this.props; 9 | 10 | if (impersonatedUser) { 11 | return ; 12 | } 13 | 14 | return ( 15 |
16 | 17 | Please 18 | {' '} 19 | impersonate a user 20 | {' '} 21 | to access this page 22 | 23 |
24 | ); 25 | } 26 | } 27 | 28 | function matchStateToProps(state) { 29 | return { 30 | impersonatedUser: state.home.impersonatedUser, 31 | }; 32 | } 33 | 34 | export default connect(matchStateToProps)(LoginContainer); 35 | -------------------------------------------------------------------------------- /trader-app-ui/src/containers/LoginContainer/styles.css: -------------------------------------------------------------------------------- 1 | .auth-error-container { 2 | height: calc(100vh - 80px); 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | } 7 | 8 | .auth-error-message { 9 | font-family: Montserrat; 10 | font-size: 30px; 11 | color: #212121; 12 | text-align: center; 13 | } 14 | 15 | .auth-error-message.link { 16 | color: #01ade5; 17 | } -------------------------------------------------------------------------------- /trader-app-ui/src/containers/NavbarContainer/styles.css: -------------------------------------------------------------------------------- 1 | /* customized bootstrap navbar */ 2 | 3 | .bg-axon { 4 | background-color: #340976; 5 | } 6 | 7 | .navbar-item-list { 8 | margin-left: 100px; 9 | } 10 | 11 | .navbar-nav .nav-item { 12 | margin-right: 42px; 13 | } 14 | 15 | .current-username { 16 | font-family: Montserrat; 17 | font-size: 16px; 18 | color: white; 19 | margin: 0 20px; 20 | } 21 | 22 | /* customize the navbar link */ 23 | .bg-axon .navbar-nav .nav-link { 24 | font-family: Montserrat; 25 | font-size: 12px; 26 | font-weight: 600; 27 | font-style: normal; 28 | font-stretch: normal; 29 | line-height: normal; 30 | letter-spacing: 0px; 31 | color: #ffffff; 32 | padding-left: 0; 33 | padding-right: 0; 34 | padding-bottom: 3px; 35 | } 36 | 37 | /* customize active or hovered navbar links */ 38 | .bg-axon .nav-item .nav-link.active, 39 | .bg-axon .nav-item:hover .nav-link.active { 40 | color: #ffffff; 41 | border-bottom: 1px solid #ffffff; 42 | opacity: 1.0; 43 | } 44 | 45 | .bg-axon .nav-item:hover .nav-link { 46 | opacity: 0.5; 47 | } 48 | 49 | /* customize the brand */ 50 | .bg-axon .navbar-brand, 51 | .bg-axon .navbar-text { 52 | font-weight: bold; 53 | font-family: Roboto, Arial, sans-serif; 54 | font-size: 22px; 55 | font-weight: bold; 56 | font-style: normal; 57 | font-stretch: normal; 58 | line-height: normal; 59 | letter-spacing: 0px; 60 | color: #ffffff; 61 | } 62 | 63 | /* Medium devices (tablets, less than 992px) - bootstrap responsive breakpoint */ 64 | @media (max-width: 991.98px) { 65 | .navbar-item-list { 66 | margin-left: 0; 67 | } 68 | 69 | .current-username { 70 | margin: 20px 0; 71 | } 72 | 73 | .bg-axon .nav-item .nav-link.active, 74 | .bg-axon .nav-item:hover .nav-link.active { 75 | color: #01ade5; 76 | border-bottom: none; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /trader-app-ui/src/containers/SecureRoute.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Route, Redirect } from 'react-router-dom'; 4 | 5 | const SecureRoute = ({ component: ComposedComponent, ...rest }) => { 6 | class Authentication extends Component { 7 | // redirect if not authenticated; otherwise, return the component passed into 8 | handleRender(props) { 9 | if (!this.props.impersonatedUser) { 10 | return ; 11 | } 12 | return ; 13 | } 14 | 15 | render() { 16 | return ( 17 | 18 | ); 19 | } 20 | } 21 | 22 | function mapStateToProps(state) { 23 | return { 24 | impersonatedUser: state.home.impersonatedUser, 25 | }; 26 | } 27 | 28 | const AuthenticationContainer = connect(mapStateToProps)(Authentication); 29 | return ; 30 | }; 31 | 32 | export default SecureRoute; 33 | -------------------------------------------------------------------------------- /trader-app-ui/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | height: 100vh; 3 | margin: 0; 4 | padding: 0; 5 | font-family: Roboto, Arial, sans-serif; 6 | } 7 | 8 | .text-centering { 9 | text-align: center; 10 | } 11 | 12 | .centered-loader { 13 | position: absolute; 14 | margin: 0 auto; 15 | left: 50%; 16 | top: 50%; 17 | margin-left: -80px; 18 | } 19 | 20 | .small-divider-white { 21 | background-color: #FFFFFF; 22 | height: 3px; 23 | width: 22px; 24 | margin-bottom: 15px; 25 | } 26 | 27 | .btn-primary.axon-button { 28 | background-color: #01ade5; 29 | border-color: #01ade5; 30 | color: white; 31 | font-family: Montserrat; 32 | font-size: 12px; 33 | font-weight: 600; 34 | } 35 | 36 | .btn-light.axon-button { 37 | background-color: transparent; 38 | border-color: white; 39 | color: white; 40 | font-family: Montserrat; 41 | font-size: 12px; 42 | } 43 | 44 | .btn-light.axon-button:hover { 45 | background-color: white; 46 | color: #01ade5; 47 | } 48 | 49 | .axon-error { 50 | font-family: Montserrat, Arial, Helvetica, sans-serif; 51 | font-size: 24px; 52 | font-weight: 600; 53 | font-style: normal; 54 | font-stretch: normal; 55 | line-height: 1.38; 56 | letter-spacing: normal; 57 | color: #e74c3c; 58 | } 59 | 60 | .table-responsive { 61 | max-height: 500px; 62 | overflow-y:auto; 63 | } 64 | -------------------------------------------------------------------------------- /trader-app-ui/src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import './index.css'; 4 | import App from './containers/App'; 5 | import registerServiceWorker from './registerServiceWorker'; 6 | 7 | if (process.env.NODE_ENV !== 'production') { 8 | require('dotenv').load(); 9 | } 10 | 11 | ReactDOM.render(, document.getElementById('root')); 12 | registerServiceWorker(); 13 | -------------------------------------------------------------------------------- /trader-app-ui/src/reducers/home.js: -------------------------------------------------------------------------------- 1 | import { 2 | FETCH_USERS_REQUEST, 3 | FETCH_USERS_SUCCESS, 4 | FETCH_USERS_FAILURE, 5 | SET_IMPERSONATED_USER, 6 | LOGOUT_USER, 7 | } from '../constants/homeActions'; 8 | 9 | const initialState = { 10 | isFetching: false, 11 | error: null, 12 | users: [], 13 | impersonatedUser: null, 14 | }; 15 | 16 | export default function homeReducer(state = initialState, action) { 17 | switch (action.type) { 18 | case FETCH_USERS_REQUEST: 19 | return { 20 | ...state, 21 | isFetching: action.payload.isFetching, 22 | error: null, 23 | }; 24 | case FETCH_USERS_SUCCESS: 25 | return { 26 | ...state, 27 | isFetching: action.payload.isFetching, 28 | users: action.payload.data, 29 | }; 30 | case FETCH_USERS_FAILURE: 31 | return { 32 | ...state, 33 | isFetching: action.payload.isFetching, 34 | error: action.payload.error, 35 | }; 36 | case SET_IMPERSONATED_USER: 37 | return { 38 | ...state, 39 | impersonatedUser: action.payload.impersonatedUser, 40 | }; 41 | case LOGOUT_USER: 42 | return { 43 | ...state, 44 | impersonatedUser: null, 45 | }; 46 | default: 47 | return state; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /trader-app-ui/src/reducers/home.test.js: -------------------------------------------------------------------------------- 1 | import homeReducer from './home'; 2 | import * as actions from '../constants/homeActions'; 3 | 4 | describe('home reducer', () => { 5 | it('should return the initial state', () => { 6 | expect(homeReducer(undefined, {})).toEqual( 7 | { 8 | isFetching: false, 9 | error: null, 10 | users: [], 11 | impersonatedUser: null, 12 | }, 13 | ); 14 | }); 15 | 16 | it('should handle FETCH_USERS_REQUEST', () => { 17 | expect(homeReducer({}, { 18 | type: actions.FETCH_USERS_REQUEST, 19 | payload: { 20 | isFetching: true, 21 | }, 22 | })).toEqual( 23 | { 24 | isFetching: true, 25 | error: null, 26 | }, 27 | ); 28 | }); 29 | 30 | it('should handle FETCH_USERS_SUCCESS', () => { 31 | expect(homeReducer({}, { 32 | type: actions.FETCH_USERS_SUCCESS, 33 | payload: { 34 | isFetching: true, 35 | data: [ 36 | { identifier: '9834kdjshg', name: 'Buyer One' }, 37 | { identifier: 'dfsdf34563', name: 'Buyer Two' }, 38 | ], 39 | }, 40 | })).toEqual( 41 | { 42 | isFetching: true, 43 | users: [ 44 | { identifier: '9834kdjshg', name: 'Buyer One' }, 45 | { identifier: 'dfsdf34563', name: 'Buyer Two' }, 46 | ], 47 | }, 48 | ); 49 | }); 50 | 51 | it('should handle FETCH_USERS_FAILURE', () => { 52 | expect(homeReducer({}, { 53 | type: actions.FETCH_USERS_FAILURE, 54 | payload: { 55 | isFetching: false, 56 | error: { 57 | message: 'Not Found', 58 | }, 59 | 60 | }, 61 | })).toEqual( 62 | { 63 | isFetching: false, 64 | error: { 65 | message: 'Not Found', 66 | }, 67 | }, 68 | ); 69 | }); 70 | 71 | it('should handle SET_IMPERSONATED_USER', () => { 72 | expect(homeReducer({}, { 73 | type: actions.SET_IMPERSONATED_USER, 74 | payload: { 75 | impersonatedUser: { identifier: 'kdjs333', name: 'Buyer One' }, 76 | }, 77 | })).toEqual( 78 | { 79 | impersonatedUser: { identifier: 'kdjs333', name: 'Buyer One' }, 80 | }, 81 | ); 82 | }); 83 | 84 | it('should handle LOGOUT_USER', () => { 85 | expect(homeReducer({}, { 86 | type: actions.LOGOUT_USER, 87 | })).toEqual( 88 | { 89 | impersonatedUser: null, 90 | }, 91 | ); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /trader-app-ui/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import { routerReducer } from 'react-router-redux'; 3 | import companyReducer from './company'; 4 | import homeReducer from './home'; 5 | import portfolioReducer from './portfolio'; 6 | 7 | const rootReducer = combineReducers({ 8 | router: routerReducer, 9 | home: homeReducer, 10 | companies: companyReducer, 11 | portfolio: portfolioReducer, 12 | }); 13 | 14 | export default rootReducer; 15 | -------------------------------------------------------------------------------- /trader-app-ui/src/reducers/portfolio.js: -------------------------------------------------------------------------------- 1 | import { 2 | FETCH_PORTFOLIO_REQUEST, 3 | FETCH_PORTFOLIO_SUCCESS, 4 | FETCH_PORTFOLIO_FAILURE, 5 | FETCH_TRANSACTIONS_BY_PORTFOLIOID_REQUEST, 6 | FETCH_TRANSACTIONS_BY_PORTFOLIOID_SUCCESS, 7 | FETCH_TRANSACTIONS_BY_PORTFOLIOID_FAILURE, 8 | } from '../constants/portfolioActions'; 9 | 10 | // expected format of the portfolio 11 | 12 | // const portfolio = { 13 | // identifier: '392473kjshdfjkh', 14 | // userId: '34935kjehsfjkhsd', 15 | // amountOfMoney: 0, 16 | // reservedAmountOfMoney: 0, 17 | // itemsInPossession: { 18 | // '4798375kjhsdfjksh': { 19 | // generatedId: 23, 20 | // identifier: '347293kjdhfjks', 21 | // companyId: '35794skjdhfjkdshjkf', 22 | // companyName: 'Pivotal', 23 | // amount: 1000 24 | // } 25 | // itemsReserved: {}, 26 | // } 27 | 28 | const initialState = { 29 | isFetching: false, 30 | error: null, 31 | data: null, 32 | transactions: { 33 | isFetching: false, 34 | error: null, 35 | data: null, 36 | }, 37 | }; 38 | 39 | export default function portfolioReducer(state = initialState, action) { 40 | switch (action.type) { 41 | case FETCH_PORTFOLIO_REQUEST: 42 | return { 43 | ...state, 44 | isFetching: action.payload.isFetching, 45 | error: null, 46 | }; 47 | case FETCH_PORTFOLIO_SUCCESS: 48 | return { 49 | ...state, 50 | isFetching: action.payload.isFetching, 51 | data: action.payload.data, 52 | }; 53 | case FETCH_PORTFOLIO_FAILURE: 54 | return { 55 | ...state, 56 | isFetching: action.payload.isFetching, 57 | error: action.payload.error, 58 | }; 59 | case FETCH_TRANSACTIONS_BY_PORTFOLIOID_REQUEST: 60 | return { 61 | ...state, 62 | transactions: { 63 | isFetching: true, 64 | error: null, 65 | data: null, 66 | }, 67 | }; 68 | case FETCH_TRANSACTIONS_BY_PORTFOLIOID_SUCCESS: 69 | return { 70 | ...state, 71 | transactions: { 72 | isFetching: false, 73 | error: null, 74 | data: action.payload.data, 75 | }, 76 | }; 77 | case FETCH_TRANSACTIONS_BY_PORTFOLIOID_FAILURE: 78 | return { 79 | ...state, 80 | transactions: { 81 | isFetching: false, 82 | error: action.payload.error, 83 | data: null, 84 | }, 85 | }; 86 | default: 87 | return state; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /trader-app-ui/src/utils/animation.js: -------------------------------------------------------------------------------- 1 | const scrollToAnimated = (scrollTarget, duration) => { 2 | const initialY = document.body.scrollTop; 3 | const baseY = (initialY + scrollTarget) * 0.5; 4 | const difference = initialY - baseY; 5 | const startTime = performance.now(); 6 | 7 | const step = () => { 8 | let normalizedTime = (performance.now() - startTime) / duration; 9 | if (normalizedTime > 1) normalizedTime = 1; 10 | 11 | window.scrollTo(0, baseY + difference * Math.cos(normalizedTime * Math.PI)); 12 | if (normalizedTime < 1) window.requestAnimationFrame(step); 13 | }; 14 | 15 | window.requestAnimationFrame(step); 16 | }; 17 | 18 | export { scrollToAnimated }; 19 | -------------------------------------------------------------------------------- /trader-app-ui/src/utils/config.js: -------------------------------------------------------------------------------- 1 | export const HostURLsMapping = { 2 | 'localhost': 'https://esrefarch-demo-trader-app.cfapps.io', 3 | 'esrefarch-demo-trader-ui.cfapps.io': 'https://esrefarch-demo-trader-app.cfapps.io', 4 | 'axontrader.cfapps.io': 'https://esrefarch-demo-trader-app.cfapps.io', 5 | }; 6 | export const ApiConfig = () => { 7 | const hostname = window.location.hostname; 8 | const apiURL = HostURLsMapping[hostname]; 9 | return apiURL || ''; 10 | }; 11 | -------------------------------------------------------------------------------- /trader-app-ui/src/utils/fetch.js: -------------------------------------------------------------------------------- 1 | const status = (response) => { 2 | // status 200 numbers are successfull http responses 3 | if (response.status >= 200 && response.status < 300) { 4 | return Promise.resolve(response); 5 | } 6 | return Promise.reject(new Error(response.statusText)); 7 | }; 8 | 9 | const json = response => response.json(); 10 | 11 | export { status, json }; 12 | -------------------------------------------------------------------------------- /trader-app-ui/src/utils/transactions.js: -------------------------------------------------------------------------------- 1 | const formatTransactionState = (state) => { 2 | const formattedState = state.toLowerCase() 3 | .split('_') 4 | .map(s => s.charAt(0).toUpperCase() + s.substring(1)) 5 | .join(' '); 6 | 7 | return formattedState; 8 | }; 9 | 10 | export default formatTransactionState; 11 | -------------------------------------------------------------------------------- /trader-app/manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: esrefarch-demo-trader-app 4 | path: target/trader-app.jar 5 | timeout: 120 6 | instances: 1 7 | buildpacks: 8 | - java_buildpack 9 | health-check-type: port 10 | services: 11 | - appdb 12 | - rabbit 13 | - config 14 | - registry 15 | env: 16 | TRUST_CERTS: api.run.pivotal.io 17 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/app/AmqpConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.app; 2 | 3 | import com.rabbitmq.client.Channel; 4 | import org.axonframework.amqp.eventhandling.AMQPMessageConverter; 5 | import org.axonframework.amqp.eventhandling.spring.SpringAMQPMessageSource; 6 | import org.axonframework.config.EventProcessingConfiguration; 7 | import org.axonframework.eventhandling.EventMessage; 8 | import org.axonframework.messaging.SubscribableMessageSource; 9 | import org.springframework.amqp.core.*; 10 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.beans.factory.annotation.Qualifier; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.context.annotation.Configuration; 15 | import org.springframework.transaction.annotation.Transactional; 16 | 17 | @Configuration 18 | public class AmqpConfiguration { 19 | 20 | @Autowired 21 | public void defineExchange(AmqpAdmin admin) { 22 | Queue tradesQueue = QueueBuilder.durable("trades").build(); 23 | Exchange tradingExchange = ExchangeBuilder.topicExchange("trading-engine-events").build(); 24 | 25 | admin.declareExchange(tradingExchange); 26 | admin.declareQueue(tradesQueue); 27 | admin.declareBinding(BindingBuilder.bind(tradesQueue) 28 | .to(tradingExchange) 29 | .with("#") 30 | .noargs()); 31 | } 32 | 33 | @Autowired 34 | public void config(EventProcessingConfiguration epConfig, 35 | @Qualifier("trade-events") SubscribableMessageSource> tradeEvents) { 36 | epConfig.registerSubscribingEventProcessor("trading", c -> tradeEvents); 37 | } 38 | 39 | @Qualifier("trade-events") 40 | @Bean 41 | public SubscribableMessageSource> tradeEvents(AMQPMessageConverter messageConverter) { 42 | return new SpringAMQPMessageSource(messageConverter) { 43 | @Transactional 44 | @RabbitListener(queues = "trades") 45 | @Override 46 | public void onMessage(Message message, Channel channel) { 47 | super.onMessage(message, channel); 48 | } 49 | }; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/app/AppConfig.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.app; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.databind.SerializationFeature; 5 | import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator; 6 | import com.fasterxml.jackson.module.kotlin.KotlinModule; 7 | import org.axonframework.commandhandling.CommandBus; 8 | import org.axonframework.commandhandling.distributed.DistributedCommandBus; 9 | import org.axonframework.messaging.interceptors.LoggingInterceptor; 10 | import org.axonframework.serialization.Serializer; 11 | import org.axonframework.serialization.json.JacksonSerializer; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.beans.factory.annotation.Qualifier; 14 | import org.springframework.context.annotation.Bean; 15 | import org.springframework.context.annotation.Configuration; 16 | 17 | @Configuration 18 | public class AppConfig { 19 | 20 | /** 21 | * We have chosen to take this optional step in order to benefit from automatic logging of every command 22 | * message being published on to the {@link CommandBus}. These are `INFO` level statements of every action. 23 | * We have qualified the given CommandBus parameter with 'localSegment' as we are using a 24 | * {@link DistributedCommandBus} and we want to associate this {@link LoggingInterceptor} with the local CommandBus 25 | * node. 26 | * 27 | * @param commandBus the {@link CommandBus} auto configured for this application 28 | */ 29 | @Autowired 30 | public void configure(@Qualifier("localSegment") CommandBus commandBus) { 31 | commandBus.registerHandlerInterceptor(new LoggingInterceptor<>()); 32 | } 33 | 34 | @Autowired 35 | public void configure(ObjectMapper objectMapper) { 36 | objectMapper.registerModule(new KotlinModule()); 37 | objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); 38 | } 39 | 40 | @Bean 41 | @Qualifier("eventSerializer") 42 | public Serializer eventSerializer(ObjectMapper objectMapper) { 43 | return new JacksonSerializer(objectMapper); 44 | } 45 | 46 | @Bean 47 | public JsonSchemaGenerator jsonSchemaGenerator(ObjectMapper objectMapper) { 48 | return new JsonSchemaGenerator(objectMapper); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/app/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.app; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 5 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 6 | 7 | @Configuration 8 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 9 | 10 | @Override 11 | protected void configure(HttpSecurity http) throws Exception { 12 | http.cors() 13 | .and() 14 | .csrf().disable() 15 | .authorizeRequests().anyRequest().permitAll(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/app/TraderApplication.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.app; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | import org.springframework.boot.SpringApplication; 6 | import org.springframework.boot.autoconfigure.SpringBootApplication; 7 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 8 | 9 | @EnableWebSecurity 10 | @SpringBootApplication 11 | public class TraderApplication { 12 | 13 | private static final Logger LOG = LoggerFactory.getLogger(TraderApplication.class); 14 | 15 | public static void main(String[] args) { 16 | SpringApplication.run(TraderApplication.class, args); 17 | LOG.info("Started the Axon CQRS and Event Sourcing Trader-app."); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/app/command/company/CompanyOrderBookListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.pivotal.refarch.cqrs.trader.app.command.company; 18 | 19 | import io.pivotal.refarch.cqrs.trader.coreapi.company.AddOrderBookToCompanyCommand; 20 | import io.pivotal.refarch.cqrs.trader.coreapi.company.CompanyCreatedEvent; 21 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.OrderBookId; 22 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.trades.CreateOrderBookCommand; 23 | import org.axonframework.commandhandling.gateway.CommandGateway; 24 | import org.axonframework.config.ProcessingGroup; 25 | import org.axonframework.eventhandling.EventHandler; 26 | import org.slf4j.Logger; 27 | import org.slf4j.LoggerFactory; 28 | import org.springframework.stereotype.Service; 29 | 30 | @Service 31 | @ProcessingGroup("commandPublishingEventHandlers") 32 | public class CompanyOrderBookListener { 33 | 34 | private static final Logger logger = LoggerFactory.getLogger(CompanyOrderBookListener.class); 35 | 36 | private final CommandGateway commandGateway; 37 | 38 | public CompanyOrderBookListener(CommandGateway commandGateway) { 39 | this.commandGateway = commandGateway; 40 | } 41 | 42 | @EventHandler 43 | public void on(CompanyCreatedEvent event) { 44 | logger.debug("About to dispatch a new command to create an OrderBook for the company {}", event.getCompanyId()); 45 | 46 | OrderBookId orderBookId = new OrderBookId(); 47 | commandGateway.send(new CreateOrderBookCommand(orderBookId)); 48 | commandGateway.send(new AddOrderBookToCompanyCommand(event.getCompanyId(), orderBookId)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/app/command/user/PortfolioManagementUserListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2012. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.pivotal.refarch.cqrs.trader.app.command.user; 18 | 19 | import io.pivotal.refarch.cqrs.trader.app.query.users.UserCreatedEvent; 20 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.CreatePortfolioCommand; 21 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioId; 22 | import org.axonframework.commandhandling.gateway.CommandGateway; 23 | import org.axonframework.config.ProcessingGroup; 24 | import org.axonframework.eventhandling.EventHandler; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | import org.springframework.stereotype.Service; 28 | 29 | @Service 30 | @ProcessingGroup("commandPublishingEventHandlers") 31 | public class PortfolioManagementUserListener { 32 | 33 | private static final Logger logger = LoggerFactory.getLogger(PortfolioManagementUserListener.class); 34 | 35 | private final CommandGateway commandGateway; 36 | 37 | public PortfolioManagementUserListener(CommandGateway commandGateway) { 38 | this.commandGateway = commandGateway; 39 | } 40 | 41 | @EventHandler 42 | public void on(UserCreatedEvent event) { 43 | logger.debug("About to dispatch a new command to create a Portfolio for the new user {}", event.getUserId()); 44 | commandGateway.send(new CreatePortfolioCommand(new PortfolioId(), event.getUserId())); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/app/query/company/CompanyView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2012. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.pivotal.refarch.cqrs.trader.app.query.company; 18 | 19 | import org.springframework.data.annotation.Id; 20 | 21 | import javax.persistence.Entity; 22 | 23 | @Entity 24 | public class CompanyView { 25 | 26 | @Id 27 | @javax.persistence.Id 28 | private String identifier; 29 | private String name; 30 | private long value; 31 | private long amountOfShares; 32 | private boolean tradeStarted; 33 | 34 | public long getAmountOfShares() { 35 | return amountOfShares; 36 | } 37 | 38 | public void setAmountOfShares(long amountOfShares) { 39 | this.amountOfShares = amountOfShares; 40 | } 41 | 42 | public String getIdentifier() { 43 | return identifier; 44 | } 45 | 46 | public void setIdentifier(String identifier) { 47 | this.identifier = identifier; 48 | } 49 | 50 | public String getName() { 51 | return name; 52 | } 53 | 54 | public void setName(String name) { 55 | this.name = name; 56 | } 57 | 58 | public boolean isTradeStarted() { 59 | return tradeStarted; 60 | } 61 | 62 | public void setTradeStarted(boolean tradeStarted) { 63 | this.tradeStarted = tradeStarted; 64 | } 65 | 66 | public long getValue() { 67 | return value; 68 | } 69 | 70 | public void setValue(long value) { 71 | this.value = value; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/app/query/company/CompanyViewRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2012. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.pivotal.refarch.cqrs.trader.app.query.company; 18 | 19 | import org.springframework.data.jpa.repository.JpaRepository; 20 | 21 | public interface CompanyViewRepository extends JpaRepository { 22 | 23 | } 24 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/app/query/orderbook/OrderBookViewRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2012. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.pivotal.refarch.cqrs.trader.app.query.orderbook; 18 | 19 | import io.pivotal.refarch.cqrs.trader.app.query.orders.trades.OrderBookView; 20 | import org.springframework.data.jpa.repository.JpaRepository; 21 | 22 | import java.util.List; 23 | 24 | 25 | public interface OrderBookViewRepository extends JpaRepository { 26 | 27 | List findByCompanyIdentifier(String companyIdentifier); 28 | } 29 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/app/query/orders/transaction/TradeExecutedQueryRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2012. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.pivotal.refarch.cqrs.trader.app.query.orders.transaction; 18 | 19 | import org.springframework.data.jpa.repository.JpaRepository; 20 | 21 | import java.util.List; 22 | 23 | public interface TradeExecutedQueryRepository extends JpaRepository { 24 | 25 | List findByOrderBookId(String orderBookId); 26 | } 27 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/app/query/orders/transaction/TradeExecutedView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2012. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.pivotal.refarch.cqrs.trader.app.query.orders.transaction; 18 | 19 | import javax.persistence.Entity; 20 | import javax.persistence.GeneratedValue; 21 | 22 | @Entity 23 | public class TradeExecutedView { 24 | 25 | @javax.persistence.Id 26 | @GeneratedValue 27 | private Long generatedId; 28 | 29 | private long tradeCount; 30 | private long tradePrice; 31 | private String companyName; 32 | private String orderBookId; 33 | 34 | public long getTradeCount() { 35 | return tradeCount; 36 | } 37 | 38 | public void setTradeCount(long tradeCount) { 39 | this.tradeCount = tradeCount; 40 | } 41 | 42 | public String getCompanyName() { 43 | return companyName; 44 | } 45 | 46 | public void setCompanyName(String companyName) { 47 | this.companyName = companyName; 48 | } 49 | 50 | public long getTradePrice() { 51 | return tradePrice; 52 | } 53 | 54 | public void setTradePrice(long tradePrice) { 55 | this.tradePrice = tradePrice; 56 | } 57 | 58 | public String getOrderBookId() { 59 | return orderBookId; 60 | } 61 | 62 | public void setOrderBookId(String orderBookId) { 63 | this.orderBookId = orderBookId; 64 | } 65 | 66 | public Long getGeneratedId() { 67 | return generatedId; 68 | } 69 | 70 | public void setGeneratedId(Long generatedId) { 71 | this.generatedId = generatedId; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/app/query/portfolio/ItemEntry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2012. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.pivotal.refarch.cqrs.trader.app.query.portfolio; 18 | 19 | import javax.persistence.Entity; 20 | import javax.persistence.GeneratedValue; 21 | 22 | @Entity 23 | public class ItemEntry { 24 | 25 | @javax.persistence.Id 26 | @GeneratedValue 27 | private Long generatedId; 28 | 29 | private String identifier; // OrderBook identifier 30 | private String companyId; 31 | private String companyName; 32 | private long amount; 33 | 34 | public Long getGeneratedId() { 35 | return generatedId; 36 | } 37 | 38 | public void setGeneratedId(Long generatedId) { 39 | this.generatedId = generatedId; 40 | } 41 | 42 | public String getIdentifier() { 43 | return identifier; 44 | } 45 | 46 | public void setIdentifier(String identifier) { 47 | this.identifier = identifier; 48 | } 49 | 50 | public String getCompanyId() { 51 | return companyId; 52 | } 53 | 54 | public void setCompanyId(String companyId) { 55 | this.companyId = companyId; 56 | } 57 | 58 | public String getCompanyName() { 59 | return companyName; 60 | } 61 | 62 | public void setCompanyName(String companyName) { 63 | this.companyName = companyName; 64 | } 65 | 66 | public long getAmount() { 67 | return amount; 68 | } 69 | 70 | public void setAmount(long amount) { 71 | this.amount = amount; 72 | } 73 | 74 | @Override 75 | public String toString() { 76 | return "ItemEntry{" + 77 | "amount=" + amount + 78 | ", identifier='" + identifier + '\'' + 79 | ", companyId='" + companyId + '\'' + 80 | ", companyName='" + companyName + '\'' + 81 | '}'; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/app/query/portfolio/PortfolioViewRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2012. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.pivotal.refarch.cqrs.trader.app.query.portfolio; 18 | 19 | import org.springframework.data.jpa.repository.JpaRepository; 20 | 21 | public interface PortfolioViewRepository extends JpaRepository { 22 | 23 | PortfolioView findByUserId(String userId); 24 | } 25 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/app/query/transaction/TransactionViewRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2012. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.pivotal.refarch.cqrs.trader.app.query.transaction; 18 | 19 | import io.pivotal.refarch.cqrs.trader.app.query.orders.transaction.TransactionView; 20 | import org.springframework.data.jpa.repository.JpaRepository; 21 | 22 | import java.util.List; 23 | 24 | public interface TransactionViewRepository extends JpaRepository { 25 | 26 | List findByPortfolioId(String portfolioId); 27 | } 28 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/app/query/users/UserView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2012. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.pivotal.refarch.cqrs.trader.app.query.users; 18 | 19 | import org.springframework.data.annotation.Id; 20 | 21 | import javax.persistence.Entity; 22 | import java.io.Serializable; 23 | 24 | @Entity 25 | public class UserView implements Serializable { 26 | 27 | @Id 28 | @javax.persistence.Id 29 | private String identifier; 30 | private String name; 31 | private String username; 32 | 33 | public String getIdentifier() { 34 | return identifier; 35 | } 36 | 37 | public void setIdentifier(String identifier) { 38 | this.identifier = identifier; 39 | } 40 | 41 | public String getName() { 42 | return name; 43 | } 44 | 45 | public void setName(String name) { 46 | this.name = name; 47 | } 48 | 49 | public String getUsername() { 50 | return username; 51 | } 52 | 53 | public void setUsername(String username) { 54 | this.username = username; 55 | } 56 | 57 | public String getUserId() { 58 | return this.identifier; 59 | } 60 | 61 | public String getUserName() { 62 | return this.username; 63 | } 64 | 65 | public String getFullName() { 66 | return this.name; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/app/query/users/UserViewRepository.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2012. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.pivotal.refarch.cqrs.trader.app.query.users; 18 | 19 | import org.springframework.data.jpa.repository.JpaRepository; 20 | 21 | public interface UserViewRepository extends JpaRepository { 22 | 23 | UserView findByIdentifier(String identifier); 24 | } 25 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/company/CompanyId.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.company; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonValue; 5 | import org.springframework.util.Assert; 6 | 7 | import java.io.Serializable; 8 | import java.util.Objects; 9 | import java.util.UUID; 10 | 11 | public class CompanyId implements Serializable { 12 | 13 | private static final long serialVersionUID = 1284500781431396262L; 14 | 15 | private final String identifier; 16 | 17 | public CompanyId() { 18 | this(UUID.randomUUID().toString()); 19 | } 20 | 21 | @JsonCreator 22 | public CompanyId(String identifier) { 23 | Assert.notNull(identifier, "Identifier parameter may not be null"); 24 | this.identifier = identifier; 25 | } 26 | 27 | @JsonValue 28 | public String getIdentifier() { 29 | return identifier; 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(identifier); 35 | } 36 | 37 | @Override 38 | public boolean equals(Object obj) { 39 | if (this == obj) { 40 | return true; 41 | } 42 | if (obj == null || getClass() != obj.getClass()) { 43 | return false; 44 | } 45 | final CompanyId other = (CompanyId) obj; 46 | return Objects.equals(this.identifier, other.identifier); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return getIdentifier(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/company/commands.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.company 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.OrderBookId 5 | import org.axonframework.commandhandling.TargetAggregateIdentifier 6 | 7 | abstract class CompanyCommand(@TargetAggregateIdentifier open val companyId: CompanyId) 8 | 9 | data class CreateCompanyCommand( 10 | @JsonProperty("companyId") override val companyId: CompanyId = CompanyId(), 11 | @JsonProperty("companyName") val companyName: String, 12 | @JsonProperty("companyValue") val companyValue: Long, 13 | @JsonProperty("amountOfShares") val amountOfShares: Long 14 | ) : CompanyCommand(companyId) 15 | 16 | data class AddOrderBookToCompanyCommand( 17 | @JsonProperty("companyId") override val companyId: CompanyId, 18 | @JsonProperty("orderBookId") val orderBookId: OrderBookId 19 | ) : CompanyCommand(companyId) 20 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/company/events.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.company 2 | 3 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.OrderBookId 4 | 5 | data class CompanyCreatedEvent( 6 | val companyId: CompanyId, 7 | val companyName: String, 8 | val companyValue: Long, 9 | val amountOfShares: Long 10 | ) 11 | 12 | data class OrderBookAddedToCompanyEvent( 13 | val companyId: CompanyId, 14 | val orderBookId: OrderBookId 15 | ) 16 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/company/queries.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.company 2 | 3 | data class CompanyByIdQuery(val companyId: CompanyId) 4 | data class FindAllCompaniesQuery(val pageOffset: Int, val pageSize: Int) 5 | 6 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/orders/OrderBookId.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.orders; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonValue; 5 | import org.springframework.util.Assert; 6 | 7 | import java.io.Serializable; 8 | import java.util.Objects; 9 | import java.util.UUID; 10 | 11 | public class OrderBookId implements Serializable { 12 | 13 | private static final long serialVersionUID = -3483676125514883162L; 14 | 15 | private final String identifier; 16 | 17 | public OrderBookId() { 18 | this(UUID.randomUUID().toString()); 19 | } 20 | 21 | @JsonCreator 22 | public OrderBookId(String identifier) { 23 | Assert.notNull(identifier, "Identifier parameter may not be null"); 24 | this.identifier = identifier; 25 | } 26 | 27 | @JsonValue 28 | public String getIdentifier() { 29 | return identifier; 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(identifier); 35 | } 36 | 37 | @Override 38 | public boolean equals(Object obj) { 39 | if (this == obj) { 40 | return true; 41 | } 42 | if (obj == null || getClass() != obj.getClass()) { 43 | return false; 44 | } 45 | final OrderBookId other = (OrderBookId) obj; 46 | return Objects.equals(this.identifier, other.identifier); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return getIdentifier(); 52 | } 53 | } -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/orders/trades/OrderId.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.orders.trades; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonValue; 5 | import org.springframework.util.Assert; 6 | 7 | import java.io.Serializable; 8 | import java.util.Objects; 9 | import java.util.UUID; 10 | 11 | public class OrderId implements Serializable { 12 | 13 | private static final long serialVersionUID = 4034328048230397374L; 14 | 15 | private final String identifier; 16 | 17 | public OrderId() { 18 | this(UUID.randomUUID().toString()); 19 | } 20 | 21 | @JsonCreator 22 | public OrderId(String identifier) { 23 | Assert.notNull(identifier, "Identifier parameter may not be null"); 24 | this.identifier = identifier; 25 | } 26 | 27 | @JsonValue 28 | public String getIdentifier() { 29 | return identifier; 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(identifier); 35 | } 36 | 37 | @Override 38 | public boolean equals(Object obj) { 39 | if (this == obj) { 40 | return true; 41 | } 42 | if (obj == null || getClass() != obj.getClass()) { 43 | return false; 44 | } 45 | final OrderId other = (OrderId) obj; 46 | return Objects.equals(this.identifier, other.identifier); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return getIdentifier(); 52 | } 53 | } -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/orders/trades/commands.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.orders.trades 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.OrderBookId 5 | import org.axonframework.commandhandling.TargetAggregateIdentifier 6 | 7 | data class CreateOrderBookCommand( 8 | @TargetAggregateIdentifier 9 | @JsonProperty("orderBookId") val orderBookId: OrderBookId = OrderBookId() 10 | ) 11 | 12 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/orders/trades/events.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.orders.trades 2 | 3 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.OrderBookId 4 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.transaction.TransactionId 5 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioId 6 | import java.io.Serializable 7 | 8 | abstract class AbstractOrderPlacedEvent( 9 | open val orderBookId: OrderBookId, 10 | open val orderId: OrderId, 11 | open val transactionId: TransactionId, 12 | open val tradeCount: Long, 13 | open val itemPrice: Long, 14 | open val portfolioId: PortfolioId 15 | ) 16 | 17 | data class BuyOrderPlacedEvent( 18 | override val orderBookId: OrderBookId, 19 | override val orderId: OrderId, 20 | override val transactionId: TransactionId, 21 | override val tradeCount: Long, 22 | override val itemPrice: Long, 23 | override val portfolioId: PortfolioId 24 | ) : AbstractOrderPlacedEvent(orderBookId, orderId, transactionId, tradeCount, itemPrice, portfolioId) 25 | 26 | data class SellOrderPlacedEvent( 27 | override val orderBookId: OrderBookId, 28 | override val orderId: OrderId, 29 | override val transactionId: TransactionId, 30 | override val tradeCount: Long, 31 | override val itemPrice: Long, 32 | override val portfolioId: PortfolioId 33 | ) : AbstractOrderPlacedEvent(orderBookId, orderId, transactionId, tradeCount, itemPrice, portfolioId) 34 | 35 | abstract class OrderBookEvent(open val orderBookId: OrderBookId) 36 | 37 | data class OrderBookCreatedEvent(override val orderBookId: OrderBookId) : OrderBookEvent(orderBookId) 38 | 39 | data class TradeExecutedEvent( 40 | val orderBookId: OrderBookId, 41 | val tradeCount: Long, 42 | val tradePrice: Long, 43 | val buyOrderId: OrderId, 44 | val sellOrderId: OrderId, 45 | val buyTransactionId: TransactionId, 46 | val sellTransactionId: TransactionId 47 | ) : Serializable { 48 | companion object { 49 | private const val serialVersionUID = 6292249351659536792L 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/orders/trades/queries.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.orders.trades 2 | 3 | import io.pivotal.refarch.cqrs.trader.coreapi.company.CompanyId 4 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.OrderBookId 5 | 6 | data class OrderBookByIdQuery(val orderBookId: OrderBookId) 7 | data class OrderBooksByCompanyIdQuery(val companyId : CompanyId) -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/orders/transaction/TransactionId.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.orders.transaction; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonValue; 5 | import org.springframework.util.Assert; 6 | 7 | import java.io.Serializable; 8 | import java.util.Objects; 9 | import java.util.UUID; 10 | 11 | public class TransactionId implements Serializable { 12 | 13 | private static final long serialVersionUID = -5267104328616955617L; 14 | 15 | private final String identifier; 16 | 17 | public TransactionId() { 18 | this(UUID.randomUUID().toString()); 19 | } 20 | 21 | @JsonCreator 22 | public TransactionId(String identifier) { 23 | Assert.notNull(identifier, "Identifier parameter may not be null"); 24 | this.identifier = identifier; 25 | } 26 | 27 | @JsonValue 28 | public String getIdentifier() { 29 | return identifier; 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(identifier); 35 | } 36 | 37 | @Override 38 | public boolean equals(Object obj) { 39 | if (this == obj) { 40 | return true; 41 | } 42 | if (obj == null || getClass() != obj.getClass()) { 43 | return false; 44 | } 45 | final TransactionId other = (TransactionId) obj; 46 | return Objects.equals(this.identifier, other.identifier); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return getIdentifier(); 52 | } 53 | } -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/orders/transaction/queries.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.orders.transaction 2 | 3 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.OrderBookId 4 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioId 5 | 6 | data class TransactionByIdQuery(val transactionId: TransactionId) 7 | data class TransactionsByPortfolioIdQuery(val portfolioId: PortfolioId) 8 | 9 | data class ExecutedTradesByOrderBookIdQuery(val orderBookId: OrderBookId) -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/orders/transaction/valueObjects.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.orders.transaction 2 | 3 | enum class TransactionState { 4 | STARTED, CONFIRMED, CANCELLED, EXECUTED, PARTIALLY_EXECUTED 5 | } 6 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/orders/valueObjects.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.orders 2 | 3 | enum class TransactionType { 4 | SELL, BUY 5 | } -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/portfolio/PortfolioId.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.portfolio; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonValue; 5 | import org.springframework.util.Assert; 6 | 7 | import java.io.Serializable; 8 | import java.util.Objects; 9 | import java.util.UUID; 10 | 11 | public class PortfolioId implements Serializable { 12 | 13 | private static final long serialVersionUID = 5649005745169990400L; 14 | 15 | private final String identifier; 16 | 17 | public PortfolioId() { 18 | this(UUID.randomUUID().toString()); 19 | } 20 | 21 | @JsonCreator 22 | public PortfolioId(String identifier) { 23 | Assert.notNull(identifier, "Identifier parameter may not be null"); 24 | this.identifier = identifier; 25 | } 26 | 27 | @JsonValue 28 | public String getIdentifier() { 29 | return identifier; 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(identifier); 35 | } 36 | 37 | @Override 38 | public boolean equals(Object obj) { 39 | if (this == obj) { 40 | return true; 41 | } 42 | if (obj == null || getClass() != obj.getClass()) { 43 | return false; 44 | } 45 | final PortfolioId other = (PortfolioId) obj; 46 | return Objects.equals(this.identifier, other.identifier); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return getIdentifier(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/portfolio/cash/commands.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.portfolio.cash 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.transaction.TransactionId 5 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioCommand 6 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioId 7 | import javax.validation.constraints.Min 8 | 9 | data class CancelCashReservationCommand( 10 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId, 11 | @JsonProperty("transactionId") val transactionId: TransactionId, 12 | @JsonProperty("amountOfMoneyToCancel") val amountOfMoneyToCancel: Long 13 | ) : PortfolioCommand(portfolioId) 14 | 15 | data class ConfirmCashReservationCommand( 16 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId, 17 | @JsonProperty("transactionId") val transactionId: TransactionId, 18 | @JsonProperty("amountOfMoneyToConfirmInCents") val amountOfMoneyToConfirmInCents: Long 19 | ) : PortfolioCommand(portfolioId) 20 | 21 | data class DepositCashCommand( 22 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId, 23 | @JsonProperty("moneyToAddInCents") @Min(0) val moneyToAddInCents: Long 24 | ) : PortfolioCommand(portfolioId) 25 | 26 | data class ReserveCashCommand( 27 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId, 28 | @JsonProperty("transactionId") val transactionId: TransactionId, 29 | @JsonProperty("amountOfMoneyToReserve") @Min(0) val amountOfMoneyToReserve: Long 30 | ) : PortfolioCommand(portfolioId) 31 | 32 | data class WithdrawCashCommand( 33 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId, 34 | @JsonProperty("amountToPayInCents") @Min(0) val amountToPayInCents: Long 35 | ) : PortfolioCommand(portfolioId) 36 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/portfolio/cash/events.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.portfolio.cash 2 | 3 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.transaction.TransactionId 4 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioEvent 5 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioId 6 | 7 | data class CashDepositedEvent( 8 | override val portfolioId: PortfolioId, 9 | val moneyAddedInCents: Long 10 | ) : PortfolioEvent(portfolioId) 11 | 12 | data class CashReservationCancelledEvent( 13 | override val portfolioId: PortfolioId, 14 | val transactionId: TransactionId, 15 | val amountOfMoneyToCancel: Long 16 | ) : PortfolioEvent(portfolioId) 17 | 18 | data class CashReservationConfirmedEvent( 19 | override val portfolioId: PortfolioId, 20 | val transactionId: TransactionId, 21 | val amountOfMoneyConfirmedInCents: Long 22 | ) : PortfolioEvent(portfolioId) 23 | 24 | data class CashReservationRejectedEvent( 25 | override val portfolioId: PortfolioId, 26 | val transactionId: TransactionId, 27 | val amountToPayInCents: Long 28 | ) : PortfolioEvent(portfolioId) 29 | 30 | data class CashReservedEvent( 31 | override val portfolioId: PortfolioId, 32 | val transactionId: TransactionId, 33 | val amountToReserve: Long 34 | ) : PortfolioEvent(portfolioId) 35 | 36 | data class CashWithdrawnEvent( 37 | override val portfolioId: PortfolioId, 38 | val amountPaidInCents: Long 39 | ) : PortfolioEvent(portfolioId) 40 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/portfolio/commands.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.portfolio 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import io.pivotal.refarch.cqrs.trader.coreapi.users.UserId 5 | import org.axonframework.commandhandling.TargetAggregateIdentifier 6 | 7 | abstract class PortfolioCommand(@TargetAggregateIdentifier open val portfolioId: PortfolioId) 8 | 9 | data class CreatePortfolioCommand( 10 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId = PortfolioId(), 11 | @JsonProperty("userId") val userId: UserId 12 | ) : PortfolioCommand(portfolioId) 13 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/portfolio/events.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.portfolio 2 | 3 | import io.pivotal.refarch.cqrs.trader.coreapi.users.UserId 4 | 5 | abstract class PortfolioEvent(open val portfolioId: PortfolioId) 6 | 7 | class PortfolioCreatedEvent( 8 | override val portfolioId: PortfolioId, 9 | val userId: UserId 10 | ) : PortfolioEvent(portfolioId) 11 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/portfolio/queries.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.portfolio 2 | 3 | import io.pivotal.refarch.cqrs.trader.coreapi.users.UserId 4 | 5 | data class PortfolioByIdQuery(val portfolioId: PortfolioId) 6 | data class PortfolioByUserIdQuery(val userId: UserId) 7 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/portfolio/stock/commands.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.portfolio.stock 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.OrderBookId 5 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.transaction.TransactionId 6 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioCommand 7 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioId 8 | import javax.validation.constraints.Min 9 | 10 | data class AddItemsToPortfolioCommand( 11 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId, 12 | @JsonProperty("orderBookId") val orderBookId: OrderBookId, 13 | @JsonProperty("amountOfItemsToAdd") @Min(0) val amountOfItemsToAdd: Long 14 | ) : PortfolioCommand(portfolioId) 15 | 16 | data class CancelItemReservationForPortfolioCommand( 17 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId, 18 | @JsonProperty("orderBookId") val orderBookId: OrderBookId, 19 | @JsonProperty("transactionId") val transactionId: TransactionId, 20 | @JsonProperty("amountOfItemsToCancel") val amountOfItemsToCancel: Long 21 | ) : PortfolioCommand(portfolioId) 22 | 23 | data class ConfirmItemReservationForPortfolioCommand( 24 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId, 25 | @JsonProperty("orderBookId") val orderBookId: OrderBookId, 26 | @JsonProperty("transactionId") val transactionId: TransactionId, 27 | @JsonProperty("amountOfItemsToConfirm") val amountOfItemsToConfirm: Long 28 | ) : PortfolioCommand(portfolioId) 29 | 30 | data class ReserveItemsCommand( 31 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId, 32 | @JsonProperty("orderBookId") val orderBookId: OrderBookId, 33 | @JsonProperty("transactionId") val transactionId: TransactionId, 34 | @JsonProperty("amountOfItemsToReserve") val amountOfItemsToReserve: Long 35 | ) : PortfolioCommand(portfolioId) 36 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/portfolio/stock/events.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.portfolio.stock 2 | 3 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.OrderBookId 4 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.transaction.TransactionId 5 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioEvent 6 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioId 7 | 8 | data class ItemReservationCancelledForPortfolioEvent( 9 | override val portfolioId: PortfolioId, 10 | val orderBookId: OrderBookId, 11 | val transactionId: TransactionId, 12 | val amountOfCancelledItems: Long 13 | ) : PortfolioEvent(portfolioId) 14 | 15 | data class ItemReservationConfirmedForPortfolioEvent( 16 | override val portfolioId: PortfolioId, 17 | val orderBookId: OrderBookId, 18 | val transactionId: TransactionId, 19 | val amountOfConfirmedItems: Long 20 | ) : PortfolioEvent(portfolioId) 21 | 22 | data class ItemsAddedToPortfolioEvent( 23 | override val portfolioId: PortfolioId, 24 | val orderBookId: OrderBookId, 25 | val amountOfItemsAdded: Long 26 | ) : PortfolioEvent(portfolioId) 27 | 28 | data class ItemsReservedEvent( 29 | override val portfolioId: PortfolioId, 30 | val orderBookId: OrderBookId, 31 | val transactionId: TransactionId, 32 | val amountOfItemsReserved: Long 33 | ) : PortfolioEvent(portfolioId) 34 | 35 | data class ItemToReserveNotAvailableInPortfolioEvent( 36 | override val portfolioId: PortfolioId, 37 | val orderBookId: OrderBookId, 38 | val transactionId: TransactionId 39 | ) : PortfolioEvent(portfolioId) 40 | 41 | data class NotEnoughItemsAvailableToReserveInPortfolioEvent( 42 | override val portfolioId: PortfolioId, 43 | val orderBookId: OrderBookId, 44 | val transactionId: TransactionId, 45 | val availableAmountOfItems: Long, 46 | val amountOfItemsToReserve: Long 47 | ) : PortfolioEvent(portfolioId) 48 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/users/UserId.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.users; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonValue; 5 | import org.springframework.util.Assert; 6 | 7 | import java.io.Serializable; 8 | import java.util.Objects; 9 | import java.util.UUID; 10 | 11 | public class UserId implements Serializable { 12 | 13 | private static final long serialVersionUID = -4860092244272266543L; 14 | 15 | private final String identifier; 16 | 17 | public UserId() { 18 | this(UUID.randomUUID().toString()); 19 | } 20 | 21 | @JsonCreator 22 | public UserId(String identifier) { 23 | Assert.notNull(identifier, "Identifier parameter may not be null"); 24 | this.identifier = identifier; 25 | } 26 | 27 | @JsonValue 28 | public String getIdentifier() { 29 | return identifier; 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(identifier); 35 | } 36 | 37 | @Override 38 | public boolean equals(Object obj) { 39 | if (this == obj) { 40 | return true; 41 | } 42 | if (obj == null || getClass() != obj.getClass()) { 43 | return false; 44 | } 45 | final UserId other = (UserId) obj; 46 | return Objects.equals(this.identifier, other.identifier); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return getIdentifier(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/users/commands.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.users 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import org.axonframework.commandhandling.TargetAggregateIdentifier 5 | import java.util.* 6 | import javax.validation.constraints.NotNull 7 | import javax.validation.constraints.Size 8 | 9 | abstract class UserCommand(@TargetAggregateIdentifier open val userId: UserId) 10 | 11 | class CreateUserCommand( 12 | @JsonProperty("userId") override val userId: UserId = UserId(), 13 | @JsonProperty("name") val name: String, @NotNull @Size(min = 3) 14 | @JsonProperty("username") val username: String, @NotNull @Size(min = 3) 15 | @JsonProperty("password") val password: String 16 | ) : UserCommand(userId) 17 | 18 | data class AuthenticateUserCommand( 19 | @JsonProperty("userId") override val userId: UserId, 20 | @JsonProperty("userName") val userName: String, 21 | @JsonProperty("password") @NotNull @Size(min = 3) val password: CharArray 22 | ) : UserCommand(userId) { 23 | 24 | override fun equals(other: Any?): Boolean { 25 | if (this === other) return true 26 | if (other !is AuthenticateUserCommand) return false 27 | 28 | if (userId != other.userId) return false 29 | if (userName != other.userName) return false 30 | if (!Arrays.equals(password, other.password)) return false 31 | 32 | return true 33 | } 34 | 35 | override fun hashCode(): Int { 36 | var result = userId.hashCode() 37 | result = 31 * result + userName.hashCode() 38 | result = 31 * result + Arrays.hashCode(password) 39 | return result 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/users/events.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.app.query.users 2 | 3 | import io.pivotal.refarch.cqrs.trader.coreapi.users.UserId 4 | 5 | abstract class UserEvent(open val userId: UserId) 6 | 7 | data class UserCreatedEvent( 8 | override val userId: UserId, 9 | val name: String, 10 | val username: String, 11 | val password: String 12 | ) : UserEvent(userId) 13 | 14 | data class UserAuthenticatedEvent(override val userId: UserId) : UserEvent(userId) 15 | -------------------------------------------------------------------------------- /trader-app/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/users/queries.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.users 2 | 3 | data class UserByIdQuery(val userId: UserId) 4 | data class FindAllUsersQuery(val pageOffset: Int, val pageSize: Int) 5 | -------------------------------------------------------------------------------- /trader-app/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #spring.h2.console.enabled=true 2 | #spring.main.banner-mode=off 3 | 4 | # Set Axon to use HA when possible 5 | axon.distributed.enabled=true 6 | 7 | # We use Eureka, so the Fallback approach shouldn't be necessary 8 | axon.distributed.spring-cloud.fallback-to-http-get=false 9 | 10 | # Axon AMQP messaging 11 | axon.amqp.exchange=trading-engine-events 12 | axon.amqp.transaction-mode=publisher_ack 13 | 14 | # Actuator 15 | management.endpoints.enabled-by-default=true 16 | management.endpoints.web.exposure.include=* 17 | management.cloudfoundry.enabled=true 18 | 19 | # Setup so that applications in the registry have the info they need to communicate directly 20 | spring.cloud.services.registrationMethod=direct 21 | 22 | # Set the general logging level within the application 23 | logging.level.io.pivotal.refarch=DEBUG 24 | 25 | # Open Session In View in combination with Server Sent Events is connection trouble waiting to happen 26 | spring.jpa.open-in-view=false 27 | 28 | -------------------------------------------------------------------------------- /trader-app/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=trader-app 2 | #spring.main.banner-mode=off 3 | #spring.security.user.roles=ADMIN 4 | 5 | spring.jpa.generate-ddl=true 6 | spring.jpa.hibernate.ddl-auto=create 7 | spring.jpa.show-sql=true 8 | 9 | #spring.jpa.database=mysql 10 | #spring.jpa.database=h2 11 | #spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect 12 | #spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect 13 | -------------------------------------------------------------------------------- /trader-app/src/test/java/io/pivotal/refarch/cqrs/trader/app/command/company/CompanyOrderBookListenerTest.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.app.command.company; 2 | 3 | import io.pivotal.refarch.cqrs.trader.coreapi.company.AddOrderBookToCompanyCommand; 4 | import io.pivotal.refarch.cqrs.trader.coreapi.company.CompanyCreatedEvent; 5 | import io.pivotal.refarch.cqrs.trader.coreapi.company.CompanyId; 6 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.trades.CreateOrderBookCommand; 7 | import org.axonframework.commandhandling.gateway.CommandGateway; 8 | import org.junit.*; 9 | import org.mockito.*; 10 | 11 | import java.util.List; 12 | 13 | import static org.junit.Assert.*; 14 | import static org.mockito.Mockito.*; 15 | 16 | public class CompanyOrderBookListenerTest { 17 | 18 | private final CommandGateway commandGateway = mock(CommandGateway.class); 19 | 20 | private CompanyOrderBookListener testSubject = new CompanyOrderBookListener(commandGateway); 21 | 22 | @Test 23 | public void testOnCompanyCreatedEventACreatesAndAssociatesOrderBookCommandIsPublished() { 24 | CompanyId testCompanyId = new CompanyId(); 25 | 26 | testSubject.on(new CompanyCreatedEvent(testCompanyId, "AxonIQ", 1337L, 50L)); 27 | 28 | ArgumentCaptor commandCaptor = ArgumentCaptor.forClass(Object.class); 29 | 30 | verify(commandGateway, times(2)).send(commandCaptor.capture()); 31 | 32 | List results = commandCaptor.getAllValues(); 33 | CreateOrderBookCommand firstCommand = (CreateOrderBookCommand) results.get(0); 34 | assertNotNull(firstCommand); 35 | assertNotNull(firstCommand.getOrderBookId()); 36 | AddOrderBookToCompanyCommand secondCommand = (AddOrderBookToCompanyCommand) results.get(1); 37 | assertNotNull(secondCommand); 38 | assertEquals(testCompanyId, secondCommand.getCompanyId()); 39 | assertNotNull(secondCommand.getOrderBookId()); 40 | } 41 | } -------------------------------------------------------------------------------- /trader-app/src/test/java/io/pivotal/refarch/cqrs/trader/app/command/user/PortfolioManagementUserListenerTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010-2012. Axon Framework 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.pivotal.refarch.cqrs.trader.app.command.user; 18 | 19 | import io.pivotal.refarch.cqrs.trader.app.query.users.UserCreatedEvent; 20 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.CreatePortfolioCommand; 21 | import io.pivotal.refarch.cqrs.trader.coreapi.users.UserId; 22 | import org.axonframework.commandhandling.gateway.CommandGateway; 23 | import org.junit.*; 24 | import org.mockito.*; 25 | 26 | import static org.junit.Assert.*; 27 | import static org.mockito.Mockito.*; 28 | 29 | public class PortfolioManagementUserListenerTest { 30 | 31 | private final CommandGateway commandGateway = mock(CommandGateway.class); 32 | 33 | private final PortfolioManagementUserListener testSubject = new PortfolioManagementUserListener(commandGateway); 34 | 35 | @Test 36 | public void testOnUserCreatedEventACreatePortfolioCommandIsPublished() { 37 | UserId testUserId = new UserId(); 38 | 39 | testSubject.on(new UserCreatedEvent(testUserId, "Test", "testuser", "testpassword")); 40 | 41 | ArgumentCaptor commandCaptor = ArgumentCaptor.forClass(CreatePortfolioCommand.class); 42 | 43 | verify(commandGateway).send(commandCaptor.capture()); 44 | 45 | CreatePortfolioCommand result = commandCaptor.getValue(); 46 | assertNotNull(result); 47 | assertNotNull(result.getPortfolioId()); 48 | assertEquals(testUserId, result.getUserId()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postAddItemsToPortfolioCommandShouldReturnOk.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/AddItemsToPortfolioCommand" 10 | body( 11 | "portfolioId": anyUuid(), 12 | "orderBookId": anyUuid(), 13 | "amountOfItemsToAdd": anyPositiveInt() 14 | ) 15 | headers { 16 | contentType applicationJson() 17 | } 18 | } 19 | response { 20 | status HttpStatus.OK.value() 21 | async() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postAddOrderBookToCompanyCommandShouldReturnOk.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/AddOrderBookToCompanyCommand" 10 | body( 11 | "companyId": anyUuid(), 12 | "orderBookId": anyUuid() 13 | ) 14 | headers { 15 | contentType applicationJson() 16 | } 17 | } 18 | response { 19 | status HttpStatus.OK.value() 20 | async() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postAuthenticateUserCommandShouldReturnOk.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/AuthenticateUserCommand" 10 | body( 11 | "userId": anyUuid(), 12 | "userName": anyNonBlankString(), 13 | "password": anyNonBlankString() 14 | ) 15 | headers { 16 | contentType applicationJson() 17 | } 18 | } 19 | response { 20 | status HttpStatus.OK.value() 21 | async() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postCancelCashReservationCommandShouldReturnOk.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/CancelCashReservationCommand" 10 | body( 11 | "portfolioId": anyUuid(), 12 | "transactionId": anyUuid(), 13 | "amountOfMoneyToCancel": anyPositiveInt() 14 | ) 15 | headers { 16 | contentType applicationJson() 17 | } 18 | } 19 | response { 20 | status HttpStatus.OK.value() 21 | async() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postCancelItemReservationForPortfolioCommandShouldReturnOk.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/CancelItemReservationForPortfolioCommand" 10 | body( 11 | "portfolioId": anyUuid(), 12 | "orderBookId": anyUuid(), 13 | "transactionId": anyUuid(), 14 | "amountOfItemsToCancel": anyPositiveInt() 15 | ) 16 | headers { 17 | contentType applicationJson() 18 | } 19 | } 20 | response { 21 | status HttpStatus.OK.value() 22 | async() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postCancelTransactionCommandShouldReturnOk.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/CancelTransactionCommand" 10 | body( 11 | "transactionId": anyUuid() 12 | ) 13 | headers { 14 | contentType applicationJson() 15 | } 16 | } 17 | response { 18 | status HttpStatus.OK.value() 19 | async() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postConfirmCashReservationCommandShouldReturnOk.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/ConfirmCashReservationCommand" 10 | body( 11 | "portfolioId": anyUuid(), 12 | "transactionId": anyUuid(), 13 | "amountOfMoneyToConfirmInCents": anyPositiveInt() 14 | ) 15 | headers { 16 | contentType applicationJson() 17 | } 18 | } 19 | response { 20 | status HttpStatus.OK.value() 21 | async() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postConfirmItemReservationForPortfolioCommandShouldReturnOk.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/ConfirmItemReservationForPortfolioCommand" 10 | body( 11 | "portfolioId": anyUuid(), 12 | "orderBookId": anyUuid(), 13 | "transactionId": anyUuid(), 14 | "amountOfItemsToConfirm": anyPositiveInt() 15 | ) 16 | headers { 17 | contentType applicationJson() 18 | } 19 | } 20 | response { 21 | status HttpStatus.OK.value() 22 | async() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postConfirmTransactionCommandShouldReturnOk.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/ConfirmTransactionCommand" 10 | body( 11 | "transactionId": anyUuid() 12 | ) 13 | headers { 14 | contentType applicationJson() 15 | } 16 | } 17 | response { 18 | status HttpStatus.OK.value() 19 | async() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postCreateCompanyCommandShouldReturnOkAndUuid.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/CreateCompanyCommand" 10 | body( 11 | "companyName": anyNonEmptyString(), 12 | "companyValue": anyPositiveInt(), 13 | "amountOfShares": anyPositiveInt() 14 | ) 15 | headers { 16 | contentType applicationJson() 17 | } 18 | } 19 | response { 20 | status HttpStatus.OK.value() 21 | body("f82c4dd0-a785-11e8-98d0-529269fb1459") 22 | headers { 23 | contentType textPlain() 24 | } 25 | async() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postCreateOrderBookCommandShouldReturnOkAndUuid.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/CreateOrderBookCommand" 10 | body 11 | headers { 12 | contentType applicationJson() 13 | } 14 | } 15 | response { 16 | status HttpStatus.OK.value() 17 | body("f82c40ec-a785-11e8-98d0-529269fb1459") 18 | headers { 19 | contentType textPlain() 20 | } 21 | async() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postCreatePortfolioCommandShouldReturnOkAndUuid.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/CreatePortfolioCommand" 10 | body( 11 | "userId": anyUuid() 12 | ) 13 | headers { 14 | contentType applicationJson() 15 | } 16 | } 17 | response { 18 | status HttpStatus.OK.value() 19 | body("f82c481c-a785-11e8-98d0-529269fb1459") 20 | headers { 21 | contentType textPlain() 22 | } 23 | async() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postCreateUserCommandShouldReturnOkAndUuid.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/CreateUserCommand" 10 | body( 11 | "name": anyNonBlankString(), 12 | "username": anyNonBlankString(), 13 | "password": anyNonBlankString() 14 | ) 15 | headers { 16 | contentType applicationJson() 17 | } 18 | } 19 | response { 20 | status HttpStatus.OK.value() 21 | body("98684ad8-987e-4d16-8ad8-b620f4320f4c") 22 | headers { 23 | contentType textPlain() 24 | } 25 | async() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postDepositCashCommandShouldReturnOk.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/DepositCashCommand" 10 | body( 11 | "portfolioId": anyUuid(), 12 | "moneyToAddInCents": anyPositiveInt() 13 | ) 14 | headers { 15 | contentType applicationJson() 16 | } 17 | } 18 | response { 19 | status HttpStatus.OK.value() 20 | async() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postExecutedTransactionCommandShouldReturnOk.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/ExecutedTransactionCommand" 10 | body( 11 | "transactionId": anyUuid(), 12 | "amountOfItems": anyPositiveInt(), 13 | "itemPrice": anyPositiveInt() 14 | ) 15 | headers { 16 | contentType applicationJson() 17 | } 18 | } 19 | response { 20 | status HttpStatus.OK.value() 21 | async() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postOfIncorrectCommandPayloadReturnsNotFound.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/CreateCompanyCommand" 10 | body("{}") // An empty Body points to a faulty command payload 11 | headers { 12 | contentType applicationJson() 13 | } 14 | } 15 | response { 16 | status HttpStatus.NOT_FOUND.value() 17 | async() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postOfNoneExistingCommandReturnsNotFound.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/SomeNoneExistingCommand" 10 | body("{}") 11 | headers { 12 | contentType applicationJson() 13 | } 14 | } 15 | response { 16 | status HttpStatus.NOT_FOUND.value() 17 | async() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postReserveCashCommandShouldReturnOk.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/ReserveCashCommand" 10 | body( 11 | "portfolioId": anyUuid(), 12 | "transactionId": anyUuid(), 13 | "amountOfMoneyToReserve": anyPositiveInt() 14 | ) 15 | headers { 16 | contentType applicationJson() 17 | } 18 | } 19 | response { 20 | status HttpStatus.OK.value() 21 | async() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postReserveItemsCommandShouldReturnOk.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/ReserveItemsCommand" 10 | body( 11 | "portfolioId": anyUuid(), 12 | "orderBookId": anyUuid(), 13 | "transactionId": anyUuid(), 14 | "amountOfItemsToReserve": anyPositiveInt() 15 | ) 16 | headers { 17 | contentType applicationJson() 18 | } 19 | } 20 | response { 21 | status HttpStatus.OK.value() 22 | async() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postStartBuyTransactionCommandShouldReturnOkAndUuid.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/StartBuyTransactionCommand" 10 | body( 11 | "orderBookId": anyUuid(), 12 | "portfolioId": anyUuid(), 13 | "tradeCount": anyPositiveInt(), 14 | "pricePerItem": anyPositiveInt() 15 | ) 16 | headers { 17 | contentType applicationJson() 18 | } 19 | } 20 | response { 21 | status HttpStatus.OK.value() 22 | body("f82c4984-a785-11e8-98d0-529269fb1459") 23 | headers { 24 | contentType textPlain() 25 | } 26 | async() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postStartSellTransactionCommandShouldReturnOkAndUuid.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/StartSellTransactionCommand" 10 | body( 11 | "orderBookId": anyUuid(), 12 | "portfolioId": anyUuid(), 13 | "tradeCount": anyPositiveInt(), 14 | "pricePerItem": anyPositiveInt() 15 | ) 16 | headers { 17 | contentType applicationJson() 18 | } 19 | } 20 | response { 21 | status HttpStatus.OK.value() 22 | body("f82c4ace-a785-11e8-98d0-529269fb1459") 23 | headers { 24 | contentType textPlain() 25 | } 26 | async() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/command/postWithdrawCashCommandShouldReturnOk.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.command 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'POST' 9 | url "/command/WithdrawCashCommand" 10 | body( 11 | "portfolioId": anyUuid(), 12 | "amountToPayInCents": anyPositiveInt() 13 | ) 14 | headers { 15 | contentType applicationJson() 16 | } 17 | } 18 | response { 19 | status HttpStatus.OK.value() 20 | async() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/findAllCompaniesShouldReturnAllCompanies.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/company" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.OK.value() 16 | body(""" 17 | [{ 18 | "identifier": "f82c40ec-a785-11e8-98d0-529269fb1459", 19 | "name": "Pivotal", 20 | "value": 1337, 21 | "amountOfShares": 42, 22 | "tradeStarted": false 23 | }] 24 | """ 25 | ) 26 | headers { 27 | contentType applicationJson() 28 | } 29 | async() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/findAllUsersQueryShouldReturnAllUsers.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/user" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.OK.value() 16 | body(""" 17 | [{ 18 | "identifier": "98684ad8-987e-4d16-8ad8-b620f4320f4c", 19 | "name": "Pieter Humphrey", 20 | "username": "john.doe", 21 | "userName": "john.doe", 22 | "fullName": "Pieter Humphrey", 23 | "userId": "98684ad8-987e-4d16-8ad8-b620f4320f4c" 24 | }] 25 | """ 26 | ) 27 | headers { 28 | contentType applicationJson() 29 | } 30 | async() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/getCompanyByIdShouldReturnCompany.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/company/f82c40ec-a785-11e8-98d0-529269fb1459" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.OK.value() 16 | body( 17 | "identifier": "f82c40ec-a785-11e8-98d0-529269fb1459", 18 | "name": "Pivotal", 19 | "value": 1337, 20 | "amountOfShares": 42, 21 | "tradeStarted": false 22 | ) 23 | headers { 24 | contentType applicationJson() 25 | } 26 | async() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/getCompanyByIdShouldReturnNotFoundForNonExistingId.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/company/non-existing-id" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.NOT_FOUND.value() 16 | async() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/getExecutedTradesByOrderBookIdShouldNotFoundForNonExistingId.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/executed-trades/non-existing-id" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.NOT_FOUND.value() 16 | async() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/getExecutedTradesByOrderBookIdShouldReturnExecutedTrades.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/executed-trades/f82c40ec-a785-11e8-98d0-529269fb1459" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.OK.value() 16 | body([ 17 | "generatedId": 1, 18 | "tradeCount" : 30, 19 | "tradePrice" : 180, 20 | "companyName": "Pivotal", 21 | "orderBookId": "f82c40ec-a785-11e8-98d0-529269fb1459" 22 | ]) 23 | headers { 24 | contentType applicationJson() 25 | } 26 | async() 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/getOrderBookByIdShouldReturnNotFoundForNonExistingId.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/order-book/non-existing-id" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.NOT_FOUND.value() 16 | async() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/getOrderBookByIdShouldReturnOrderBook.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/order-book/f82c40ec-a785-11e8-98d0-529269fb1459" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.OK.value() 16 | body( 17 | "identifier": "f82c40ec-a785-11e8-98d0-529269fb1459", 18 | "companyIdentifier": "f82c40ec-a785-11e8-98d0-529269fb1459", 19 | "companyName": "Pivotal", 20 | "sellOrders": [ 21 | "jpaId" : 2, 22 | "identifier" : "f82c4ace-a785-11e8-98d0-529269fb1459", 23 | "tradeCount" : 5, 24 | "itemPrice" : 75, 25 | "userId" : "98684ad8-987e-4d16-8ad8-b620f4320f4c", 26 | "itemsRemaining": 20, 27 | "type" : "Sell" 28 | ], 29 | "buyOrders": [ 30 | "jpaId" : 1, 31 | "identifier" : "f82c4984-a785-11e8-98d0-529269fb1459", 32 | "tradeCount" : 5, 33 | "itemPrice" : 50, 34 | "userId" : "98684ad8-987e-4d16-8ad8-b620f4320f4c", 35 | "itemsRemaining": 10, 36 | "type" : "Buy" 37 | ] 38 | ) 39 | headers { 40 | contentType applicationJson() 41 | } 42 | async() 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/getOrderBooksByCompanyIdShouldReturnNotFoundForNonExistingId.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/order-book/by-company/non-existing-id" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.NOT_FOUND.value() 16 | async() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/getOrderBooksByCompanyIdShouldReturnOrderBook.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/order-book/by-company/f82c40ec-a785-11e8-98d0-529269fb1459" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.OK.value() 16 | body(""" 17 | [ { 18 | "identifier" : "f82c40ec-a785-11e8-98d0-529269fb1459", 19 | "companyIdentifier" : "f82c40ec-a785-11e8-98d0-529269fb1459", 20 | "companyName" : "Pivotal", 21 | "sellOrders" : [ { 22 | "jpaId" : 2, 23 | "identifier" : "f82c4ace-a785-11e8-98d0-529269fb1459", 24 | "tradeCount" : 5, 25 | "itemPrice" : 75, 26 | "userId" : "98684ad8-987e-4d16-8ad8-b620f4320f4c", 27 | "itemsRemaining" : 20, 28 | "type" : "Sell" 29 | } ], 30 | "buyOrders" : [ { 31 | "jpaId" : 1, 32 | "identifier" : "f82c4984-a785-11e8-98d0-529269fb1459", 33 | "tradeCount" : 5, 34 | "itemPrice" : 50, 35 | "userId" : "98684ad8-987e-4d16-8ad8-b620f4320f4c", 36 | "itemsRemaining" : 10, 37 | "type" : "Buy" 38 | } ] 39 | } ] 40 | """) 41 | headers { 42 | contentType applicationJson() 43 | } 44 | async() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/getPortfolioByIdShouldReturnNotFoundForNonExistingId.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/portfolio/non-existing-id" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.NOT_FOUND.value() 16 | async() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/getPortfolioByIdShouldReturnPortfolio.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/portfolio/f82c481c-a785-11e8-98d0-529269fb1459" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.OK.value() 16 | body(""" 17 | { 18 | "identifier" : "f82c481c-a785-11e8-98d0-529269fb1459", 19 | "userId" : "98684ad8-987e-4d16-8ad8-b620f4320f4c", 20 | "amountOfMoney" : 1000000, 21 | "reservedAmountOfMoney" : 5000, 22 | "itemsInPossession" : { 23 | "f82c40ec-a785-11e8-98d0-529269fb1459" : { 24 | "generatedId" : 1, 25 | "identifier" : "f82c40ec-a785-11e8-98d0-529269fb1459", 26 | "companyId" : "f82c40ec-a785-11e8-98d0-529269fb1459", 27 | "companyName" : "Pivotal", 28 | "amount" : 321 29 | } 30 | }, 31 | "itemsReserved" : { 32 | "f82c40ec-a785-11e8-98d0-529269fb1459" : { 33 | "generatedId" : 2, 34 | "identifier" : "f82c40ec-a785-11e8-98d0-529269fb1459", 35 | "companyId" : "f82c40ec-a785-11e8-98d0-529269fb1459", 36 | "companyName" : "Pivotal", 37 | "amount" : 654 38 | } 39 | } 40 | } 41 | """) 42 | headers { 43 | contentType applicationJson() 44 | } 45 | async() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/getPortfolioByUserIdShouldReturnNotFoundForNonExistingId.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/portfolio/by-user/non-existing-id" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.NOT_FOUND.value() 16 | async() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/getPortfolioByUserIdShouldReturnPortfolio.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/portfolio/by-user/98684ad8-987e-4d16-8ad8-b620f4320f4c" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.OK.value() 16 | body(""" 17 | { 18 | "identifier" : "f82c481c-a785-11e8-98d0-529269fb1459", 19 | "userId" : "98684ad8-987e-4d16-8ad8-b620f4320f4c", 20 | "amountOfMoney" : 1000000, 21 | "reservedAmountOfMoney" : 5000, 22 | "itemsInPossession" : { 23 | "f82c40ec-a785-11e8-98d0-529269fb1459" : { 24 | "generatedId" : 1, 25 | "identifier" : "f82c40ec-a785-11e8-98d0-529269fb1459", 26 | "companyId" : "f82c40ec-a785-11e8-98d0-529269fb1459", 27 | "companyName" : "Pivotal", 28 | "amount" : 321 29 | } 30 | }, 31 | "itemsReserved" : { 32 | "f82c40ec-a785-11e8-98d0-529269fb1459" : { 33 | "generatedId" : 2, 34 | "identifier" : "f82c40ec-a785-11e8-98d0-529269fb1459", 35 | "companyId" : "f82c40ec-a785-11e8-98d0-529269fb1459", 36 | "companyName" : "Pivotal", 37 | "amount" : 654 38 | } 39 | } 40 | } 41 | """) 42 | headers { 43 | contentType applicationJson() 44 | } 45 | async() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/getTransactionByIdShouldReturnNotFoundForNonExistingId.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/transaction/non-existing-id" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.NOT_FOUND.value() 16 | async() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/getTransactionByIdShouldReturnTransaction.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/transaction/f82c448e-a785-11e8-98d0-529269fb1459" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.OK.value() 16 | body( 17 | "identifier": "f82c448e-a785-11e8-98d0-529269fb1459", 18 | "orderBookId": "f82c40ec-a785-11e8-98d0-529269fb1459", 19 | "portfolioId": "f82c481c-a785-11e8-98d0-529269fb1459", 20 | "companyName": "Pivotal", 21 | "amountOfItems": 50, 22 | "amountOfExecutedItems": 25, 23 | "pricePerItem": 123, 24 | "state": "CONFIRMED", 25 | "type": "BUY" 26 | ) 27 | headers { 28 | contentType applicationJson() 29 | } 30 | async() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/getTransactionsByPortfolioIdShouldReturnNotFoundForNonExistingId.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/transaction/by-portfolio/non-existing-id" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.NOT_FOUND.value() 16 | async() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/getTransactionsByPortfolioIdShouldReturnTransactions.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/transaction/by-portfolio/f82c481c-a785-11e8-98d0-529269fb1459" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.OK.value() 16 | body([ 17 | "identifier" : "f82c448e-a785-11e8-98d0-529269fb1459", 18 | "orderBookId" : "f82c40ec-a785-11e8-98d0-529269fb1459", 19 | "portfolioId" : "f82c481c-a785-11e8-98d0-529269fb1459", 20 | "companyName" : "Pivotal", 21 | "amountOfItems" : 50, 22 | "amountOfExecutedItems": 25, 23 | "pricePerItem" : 123, 24 | "state" : "CONFIRMED", 25 | "type" : "BUY" 26 | ]) 27 | headers { 28 | contentType applicationJson() 29 | } 30 | async() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/getUserByIdShouldReturnNotFoundForNonExistingId.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/user/non-existing-id" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.NOT_FOUND.value() 16 | async() 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /trader-app/src/test/resources/contracts/trader/query/getUserByIdShouldReturnUser.groovy: -------------------------------------------------------------------------------- 1 | package contracts.trader.query 2 | 3 | import org.springframework.cloud.contract.spec.Contract 4 | import org.springframework.http.HttpStatus 5 | 6 | Contract.make { 7 | request { 8 | method 'GET' 9 | url "/query/user/98684ad8-987e-4d16-8ad8-b620f4320f4c" 10 | headers { 11 | contentType applicationJson() 12 | } 13 | } 14 | response { 15 | status HttpStatus.OK.value() 16 | body( 17 | "identifier": "98684ad8-987e-4d16-8ad8-b620f4320f4c", 18 | "name": "Pieter Humphrey", 19 | "username": "john.doe", 20 | "userName": "john.doe", 21 | "fullName": "Pieter Humphrey", 22 | "userId": "98684ad8-987e-4d16-8ad8-b620f4320f4c" 23 | ) 24 | headers { 25 | contentType applicationJson() 26 | } 27 | async() 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /trading-engine/manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: esrefarch-demo-trading-engine 4 | path: target/trading-engine.jar 5 | timeout: 120 6 | instances: 1 7 | buildpacks: 8 | - java_buildpack 9 | health-check-type: port 10 | services: 11 | - enginedb 12 | - rabbit 13 | - config 14 | - registry 15 | env: 16 | TRUST_CERTS: api.run.pivotal.io 17 | -------------------------------------------------------------------------------- /trading-engine/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | trading-engine 7 | 0.0.1-SNAPSHOT 8 | jar 9 | 10 | Trading Engine 11 | Trading Engine 12 | 13 | 14 | io.pivotal.refarch.cqrs.trader 15 | trader 16 | 0.0.1-SNAPSHOT 17 | 18 | 19 | 20 | ${artifactId} 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /trading-engine/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/orders/OrderBookId.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.orders; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonValue; 5 | import org.springframework.util.Assert; 6 | 7 | import java.io.Serializable; 8 | import java.util.Objects; 9 | import java.util.UUID; 10 | 11 | public class OrderBookId implements Serializable { 12 | 13 | private static final long serialVersionUID = -3483676125514883162L; 14 | 15 | private final String identifier; 16 | 17 | public OrderBookId() { 18 | this(UUID.randomUUID().toString()); 19 | } 20 | 21 | @JsonCreator 22 | public OrderBookId(String identifier) { 23 | Assert.notNull(identifier, "Identifier parameter may not be null"); 24 | this.identifier = identifier; 25 | } 26 | 27 | @JsonValue 28 | public String getIdentifier() { 29 | return identifier; 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(identifier); 35 | } 36 | 37 | @Override 38 | public boolean equals(Object obj) { 39 | if (this == obj) { 40 | return true; 41 | } 42 | if (obj == null || getClass() != obj.getClass()) { 43 | return false; 44 | } 45 | final OrderBookId other = (OrderBookId) obj; 46 | return Objects.equals(this.identifier, other.identifier); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return getIdentifier(); 52 | } 53 | } -------------------------------------------------------------------------------- /trading-engine/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/orders/trades/OrderId.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.orders.trades; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonValue; 5 | import org.springframework.util.Assert; 6 | 7 | import java.io.Serializable; 8 | import java.util.Objects; 9 | import java.util.UUID; 10 | 11 | public class OrderId implements Serializable { 12 | 13 | private static final long serialVersionUID = 4034328048230397374L; 14 | 15 | private final String identifier; 16 | 17 | public OrderId() { 18 | this(UUID.randomUUID().toString()); 19 | } 20 | 21 | @JsonCreator 22 | public OrderId(String identifier) { 23 | Assert.notNull(identifier, "Identifier parameter may not be null"); 24 | this.identifier = identifier; 25 | } 26 | 27 | @JsonValue 28 | public String getIdentifier() { 29 | return identifier; 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(identifier); 35 | } 36 | 37 | @Override 38 | public boolean equals(Object obj) { 39 | if (this == obj) { 40 | return true; 41 | } 42 | if (obj == null || getClass() != obj.getClass()) { 43 | return false; 44 | } 45 | final OrderId other = (OrderId) obj; 46 | return Objects.equals(this.identifier, other.identifier); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return getIdentifier(); 52 | } 53 | } -------------------------------------------------------------------------------- /trading-engine/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/orders/trades/commands.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.orders.trades 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.OrderBookId 5 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.transaction.TransactionId 6 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioId 7 | import org.axonframework.commandhandling.TargetAggregateIdentifier 8 | import javax.validation.constraints.Min 9 | 10 | abstract class OrderBookCommand(@TargetAggregateIdentifier open val orderBookId: OrderBookId) 11 | 12 | data class CreateOrderBookCommand( 13 | @JsonProperty("orderBookId") override val orderBookId: OrderBookId = OrderBookId() 14 | ) : OrderBookCommand(orderBookId) 15 | 16 | abstract class OrderCommand( 17 | @JsonProperty("orderBookId") override val orderBookId: OrderBookId, 18 | @JsonProperty("orderId") open val orderId: OrderId, 19 | @JsonProperty("portfolioId") open val portfolioId: PortfolioId, 20 | @JsonProperty("transactionId") open val transactionId: TransactionId, 21 | @JsonProperty("tradeCount") @Min(0) open val tradeCount: Long, 22 | @JsonProperty("itemPrice") @Min(0) open val itemPrice: Long 23 | ) : OrderBookCommand(orderBookId) 24 | 25 | data class CreateBuyOrderCommand( 26 | override val orderId: OrderId, 27 | override val portfolioId: PortfolioId, 28 | override val orderBookId: OrderBookId, 29 | override val transactionId: TransactionId, 30 | override val tradeCount: Long, 31 | override val itemPrice: Long 32 | ) : OrderCommand(orderBookId, orderId, portfolioId, transactionId, tradeCount, itemPrice) 33 | 34 | data class CreateSellOrderCommand( 35 | override val orderId: OrderId, 36 | override val portfolioId: PortfolioId, 37 | override val orderBookId: OrderBookId, 38 | override val transactionId: TransactionId, 39 | override val tradeCount: Long, 40 | override val itemPrice: Long 41 | ) : OrderCommand(orderBookId, orderId, portfolioId, transactionId, tradeCount, itemPrice) 42 | -------------------------------------------------------------------------------- /trading-engine/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/orders/trades/events.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.orders.trades 2 | 3 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.OrderBookId 4 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.transaction.TransactionId 5 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioId 6 | import java.io.Serializable 7 | 8 | abstract class AbstractOrderPlacedEvent( 9 | open val orderBookId: OrderBookId, 10 | open val orderId: OrderId, 11 | open val transactionId: TransactionId, 12 | open val tradeCount: Long, 13 | open val itemPrice: Long, 14 | open val portfolioId: PortfolioId 15 | ) 16 | 17 | data class BuyOrderPlacedEvent( 18 | override val orderBookId: OrderBookId, 19 | override val orderId: OrderId, 20 | override val transactionId: TransactionId, 21 | override val tradeCount: Long, 22 | override val itemPrice: Long, 23 | override val portfolioId: PortfolioId 24 | ) : AbstractOrderPlacedEvent(orderBookId, orderId, transactionId, tradeCount, itemPrice, portfolioId) 25 | 26 | data class SellOrderPlacedEvent( 27 | override val orderBookId: OrderBookId, 28 | override val orderId: OrderId, 29 | override val transactionId: TransactionId, 30 | override val tradeCount: Long, 31 | override val itemPrice: Long, 32 | override val portfolioId: PortfolioId 33 | ) : AbstractOrderPlacedEvent(orderBookId, orderId, transactionId, tradeCount, itemPrice, portfolioId) 34 | 35 | abstract class OrderBookEvent(open val orderBookId: OrderBookId) 36 | 37 | data class OrderBookCreatedEvent(override val orderBookId: OrderBookId) : OrderBookEvent(orderBookId) 38 | 39 | data class TradeExecutedEvent( 40 | val orderBookId: OrderBookId, 41 | val tradeCount: Long, 42 | val tradePrice: Long, 43 | val buyOrderId: OrderId, 44 | val sellOrderId: OrderId, 45 | val buyTransactionId: TransactionId, 46 | val sellTransactionId: TransactionId 47 | ) : Serializable { 48 | companion object { 49 | private const val serialVersionUID = 6292249351659536792L 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /trading-engine/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/orders/transaction/TransactionId.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.orders.transaction; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonValue; 5 | import org.springframework.util.Assert; 6 | 7 | import java.io.Serializable; 8 | import java.util.Objects; 9 | import java.util.UUID; 10 | 11 | public class TransactionId implements Serializable { 12 | 13 | private static final long serialVersionUID = -5267104328616955617L; 14 | 15 | private final String identifier; 16 | 17 | public TransactionId() { 18 | this(UUID.randomUUID().toString()); 19 | } 20 | 21 | @JsonCreator 22 | public TransactionId(String identifier) { 23 | Assert.notNull(identifier, "Identifier parameter may not be null"); 24 | this.identifier = identifier; 25 | } 26 | 27 | @JsonValue 28 | public String getIdentifier() { 29 | return identifier; 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(identifier); 35 | } 36 | 37 | @Override 38 | public boolean equals(Object obj) { 39 | if (this == obj) { 40 | return true; 41 | } 42 | if (obj == null || getClass() != obj.getClass()) { 43 | return false; 44 | } 45 | final TransactionId other = (TransactionId) obj; 46 | return Objects.equals(this.identifier, other.identifier); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return getIdentifier(); 52 | } 53 | } -------------------------------------------------------------------------------- /trading-engine/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/orders/valueObjects.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.orders 2 | 3 | enum class TransactionType { 4 | SELL, BUY 5 | } -------------------------------------------------------------------------------- /trading-engine/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/portfolio/PortfolioId.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.portfolio; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonValue; 5 | import org.springframework.util.Assert; 6 | 7 | import java.io.Serializable; 8 | import java.util.Objects; 9 | import java.util.UUID; 10 | 11 | public class PortfolioId implements Serializable { 12 | 13 | private static final long serialVersionUID = 5649005745169990400L; 14 | 15 | private final String identifier; 16 | 17 | public PortfolioId() { 18 | this(UUID.randomUUID().toString()); 19 | } 20 | 21 | @JsonCreator 22 | public PortfolioId(String identifier) { 23 | Assert.notNull(identifier, "Identifier parameter may not be null"); 24 | this.identifier = identifier; 25 | } 26 | 27 | @JsonValue 28 | public String getIdentifier() { 29 | return identifier; 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(identifier); 35 | } 36 | 37 | @Override 38 | public boolean equals(Object obj) { 39 | if (this == obj) { 40 | return true; 41 | } 42 | if (obj == null || getClass() != obj.getClass()) { 43 | return false; 44 | } 45 | final PortfolioId other = (PortfolioId) obj; 46 | return Objects.equals(this.identifier, other.identifier); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return getIdentifier(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /trading-engine/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/portfolio/cash/commands.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.portfolio.cash 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.transaction.TransactionId 5 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioCommand 6 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioId 7 | import javax.validation.constraints.Min 8 | 9 | data class CancelCashReservationCommand( 10 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId, 11 | @JsonProperty("transactionId") val transactionId: TransactionId, 12 | @JsonProperty("amountOfMoneyToCancel") val amountOfMoneyToCancel: Long 13 | ) : PortfolioCommand(portfolioId) 14 | 15 | data class ConfirmCashReservationCommand( 16 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId, 17 | @JsonProperty("transactionId") val transactionId: TransactionId, 18 | @JsonProperty("amountOfMoneyToConfirmInCents") val amountOfMoneyToConfirmInCents: Long 19 | ) : PortfolioCommand(portfolioId) 20 | 21 | data class DepositCashCommand( 22 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId, 23 | @JsonProperty("moneyToAddInCents") @Min(0) val moneyToAddInCents: Long 24 | ) : PortfolioCommand(portfolioId) 25 | 26 | data class ReserveCashCommand( 27 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId, 28 | @JsonProperty("transactionId") val transactionId: TransactionId, 29 | @JsonProperty("amountOfMoneyToReserve") @Min(0) val amountOfMoneyToReserve: Long 30 | ) : PortfolioCommand(portfolioId) 31 | 32 | data class WithdrawCashCommand( 33 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId, 34 | @JsonProperty("amountToPayInCents") @Min(0) val amountToPayInCents: Long 35 | ) : PortfolioCommand(portfolioId) 36 | -------------------------------------------------------------------------------- /trading-engine/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/portfolio/cash/events.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.portfolio.cash 2 | 3 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.transaction.TransactionId 4 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioEvent 5 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioId 6 | 7 | data class CashDepositedEvent( 8 | override val portfolioId: PortfolioId, 9 | val moneyAddedInCents: Long 10 | ) : PortfolioEvent(portfolioId) 11 | 12 | data class CashReservationCancelledEvent( 13 | override val portfolioId: PortfolioId, 14 | val transactionId: TransactionId, 15 | val amountOfMoneyToCancel: Long 16 | ) : PortfolioEvent(portfolioId) 17 | 18 | data class CashReservationConfirmedEvent( 19 | override val portfolioId: PortfolioId, 20 | val transactionId: TransactionId, 21 | val amountOfMoneyConfirmedInCents: Long 22 | ) : PortfolioEvent(portfolioId) 23 | 24 | data class CashReservationRejectedEvent( 25 | override val portfolioId: PortfolioId, 26 | val transactionId: TransactionId, 27 | val amountToPayInCents: Long 28 | ) : PortfolioEvent(portfolioId) 29 | 30 | data class CashReservedEvent( 31 | override val portfolioId: PortfolioId, 32 | val transactionId: TransactionId, 33 | val amountToReserve: Long 34 | ) : PortfolioEvent(portfolioId) 35 | 36 | data class CashWithdrawnEvent( 37 | override val portfolioId: PortfolioId, 38 | val amountPaidInCents: Long 39 | ) : PortfolioEvent(portfolioId) 40 | -------------------------------------------------------------------------------- /trading-engine/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/portfolio/commands.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.portfolio 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import io.pivotal.refarch.cqrs.trader.coreapi.users.UserId 5 | import org.axonframework.commandhandling.TargetAggregateIdentifier 6 | 7 | abstract class PortfolioCommand(@TargetAggregateIdentifier open val portfolioId: PortfolioId) 8 | 9 | data class CreatePortfolioCommand( 10 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId = PortfolioId(), 11 | @JsonProperty("userId") val userId: UserId 12 | ) : PortfolioCommand(portfolioId) 13 | -------------------------------------------------------------------------------- /trading-engine/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/portfolio/events.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.portfolio 2 | 3 | import io.pivotal.refarch.cqrs.trader.coreapi.users.UserId 4 | 5 | abstract class PortfolioEvent(open val portfolioId: PortfolioId) 6 | 7 | class PortfolioCreatedEvent( 8 | override val portfolioId: PortfolioId, 9 | val userId: UserId 10 | ) : PortfolioEvent(portfolioId) 11 | -------------------------------------------------------------------------------- /trading-engine/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/portfolio/stock/commands.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.portfolio.stock 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.OrderBookId 5 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.transaction.TransactionId 6 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioCommand 7 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioId 8 | import javax.validation.constraints.Min 9 | 10 | data class AddItemsToPortfolioCommand( 11 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId, 12 | @JsonProperty("orderBookId") val orderBookId: OrderBookId, 13 | @JsonProperty("amountOfItemsToAdd") @Min(0) val amountOfItemsToAdd: Long 14 | ) : PortfolioCommand(portfolioId) 15 | 16 | data class CancelItemReservationForPortfolioCommand( 17 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId, 18 | @JsonProperty("orderBookId") val orderBookId: OrderBookId, 19 | @JsonProperty("transactionId") val transactionId: TransactionId, 20 | @JsonProperty("amountOfItemsToCancel") val amountOfItemsToCancel: Long 21 | ) : PortfolioCommand(portfolioId) 22 | 23 | data class ConfirmItemReservationForPortfolioCommand( 24 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId, 25 | @JsonProperty("orderBookId") val orderBookId: OrderBookId, 26 | @JsonProperty("transactionId") val transactionId: TransactionId, 27 | @JsonProperty("amountOfItemsToConfirm") val amountOfItemsToConfirm: Long 28 | ) : PortfolioCommand(portfolioId) 29 | 30 | data class ReserveItemsCommand( 31 | @JsonProperty("portfolioId") override val portfolioId: PortfolioId, 32 | @JsonProperty("orderBookId") val orderBookId: OrderBookId, 33 | @JsonProperty("transactionId") val transactionId: TransactionId, 34 | @JsonProperty("amountOfItemsToReserve") val amountOfItemsToReserve: Long 35 | ) : PortfolioCommand(portfolioId) 36 | -------------------------------------------------------------------------------- /trading-engine/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/portfolio/stock/events.kt: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.portfolio.stock 2 | 3 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.OrderBookId 4 | import io.pivotal.refarch.cqrs.trader.coreapi.orders.transaction.TransactionId 5 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioEvent 6 | import io.pivotal.refarch.cqrs.trader.coreapi.portfolio.PortfolioId 7 | 8 | data class ItemReservationCancelledForPortfolioEvent( 9 | override val portfolioId: PortfolioId, 10 | val orderBookId: OrderBookId, 11 | val transactionId: TransactionId, 12 | val amountOfCancelledItems: Long 13 | ) : PortfolioEvent(portfolioId) 14 | 15 | data class ItemReservationConfirmedForPortfolioEvent( 16 | override val portfolioId: PortfolioId, 17 | val orderBookId: OrderBookId, 18 | val transactionId: TransactionId, 19 | val amountOfConfirmedItems: Long 20 | ) : PortfolioEvent(portfolioId) 21 | 22 | data class ItemsAddedToPortfolioEvent( 23 | override val portfolioId: PortfolioId, 24 | val orderBookId: OrderBookId, 25 | val amountOfItemsAdded: Long 26 | ) : PortfolioEvent(portfolioId) 27 | 28 | data class ItemsReservedEvent( 29 | override val portfolioId: PortfolioId, 30 | val orderBookId: OrderBookId, 31 | val transactionId: TransactionId, 32 | val amountOfItemsReserved: Long 33 | ) : PortfolioEvent(portfolioId) 34 | 35 | data class ItemToReserveNotAvailableInPortfolioEvent( 36 | override val portfolioId: PortfolioId, 37 | val orderBookId: OrderBookId, 38 | val transactionId: TransactionId 39 | ) : PortfolioEvent(portfolioId) 40 | 41 | data class NotEnoughItemsAvailableToReserveInPortfolioEvent( 42 | override val portfolioId: PortfolioId, 43 | val orderBookId: OrderBookId, 44 | val transactionId: TransactionId, 45 | val availableAmountOfItems: Long, 46 | val amountOfItemsToReserve: Long 47 | ) : PortfolioEvent(portfolioId) 48 | -------------------------------------------------------------------------------- /trading-engine/src/main/java/io/pivotal/refarch/cqrs/trader/coreapi/users/UserId.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.coreapi.users; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonValue; 5 | import org.springframework.util.Assert; 6 | 7 | import java.io.Serializable; 8 | import java.util.Objects; 9 | import java.util.UUID; 10 | 11 | public class UserId implements Serializable { 12 | 13 | private static final long serialVersionUID = -4860092244272266543L; 14 | 15 | private final String identifier; 16 | 17 | public UserId() { 18 | this(UUID.randomUUID().toString()); 19 | } 20 | 21 | @JsonCreator 22 | public UserId(String identifier) { 23 | Assert.notNull(identifier, "Identifier parameter may not be null"); 24 | this.identifier = identifier; 25 | } 26 | 27 | @JsonValue 28 | public String getIdentifier() { 29 | return identifier; 30 | } 31 | 32 | @Override 33 | public int hashCode() { 34 | return Objects.hash(identifier); 35 | } 36 | 37 | @Override 38 | public boolean equals(Object obj) { 39 | if (this == obj) { 40 | return true; 41 | } 42 | if (obj == null || getClass() != obj.getClass()) { 43 | return false; 44 | } 45 | final UserId other = (UserId) obj; 46 | return Objects.equals(this.identifier, other.identifier); 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return getIdentifier(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /trading-engine/src/main/java/io/pivotal/refarch/cqrs/trader/tradingengine/AmqpConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.tradingengine; 2 | 3 | import org.springframework.amqp.core.AmqpAdmin; 4 | import org.springframework.amqp.core.Exchange; 5 | import org.springframework.amqp.core.ExchangeBuilder; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | @Configuration 11 | public class AmqpConfiguration { 12 | 13 | @Bean 14 | public Exchange eventsExchange() { 15 | return ExchangeBuilder.topicExchange("trading-engine-events").build(); 16 | } 17 | 18 | @Autowired 19 | public void defineExchange(AmqpAdmin admin) { 20 | admin.declareExchange(eventsExchange()); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /trading-engine/src/main/java/io/pivotal/refarch/cqrs/trader/tradingengine/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.tradingengine; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 5 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 6 | 7 | @Configuration 8 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 9 | 10 | @Override 11 | protected void configure(HttpSecurity http) throws Exception { 12 | http.csrf().disable() 13 | .authorizeRequests() 14 | .anyRequest().permitAll(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /trading-engine/src/main/java/io/pivotal/refarch/cqrs/trader/tradingengine/TradingEngineApplication.java: -------------------------------------------------------------------------------- 1 | package io.pivotal.refarch.cqrs.trader.tradingengine; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.fasterxml.jackson.module.kotlin.KotlinModule; 5 | import org.axonframework.serialization.Serializer; 6 | import org.axonframework.serialization.json.JacksonSerializer; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.beans.factory.annotation.Qualifier; 10 | import org.springframework.boot.SpringApplication; 11 | import org.springframework.boot.autoconfigure.SpringBootApplication; 12 | import org.springframework.cloud.client.discovery.EnableDiscoveryClient; 13 | import org.springframework.context.annotation.Bean; 14 | 15 | @EnableDiscoveryClient 16 | @SpringBootApplication 17 | public class TradingEngineApplication { 18 | 19 | private static final Logger LOG = LoggerFactory.getLogger(TradingEngineApplication.class); 20 | 21 | /* 22 | * There is not much to do here. Apart from integrating with Rabbit for the incoming events 23 | * and then handling those events, the rest of the functionality of the view is provided 24 | * by Spring Data Repositories. 25 | */ 26 | 27 | public static void main(String[] args) { 28 | SpringApplication.run(TradingEngineApplication.class, args); 29 | } 30 | 31 | /** 32 | * Instantiate an {@link ObjectMapper} for Jackson de-/serialization. 33 | * Additionally, a {@link KotlinModule} is registered, as the Commands, Events and Queries are written in Kotlin. 34 | * 35 | * @return an {@link ObjectMapper} for Jackson de-/serialization 36 | */ 37 | @Bean 38 | public ObjectMapper objectMapper() { 39 | ObjectMapper objectMapper = new ObjectMapper(); 40 | objectMapper.registerModule(new KotlinModule()); 41 | return objectMapper; 42 | } 43 | 44 | @Bean 45 | @Qualifier("eventSerializer") 46 | public Serializer eventSerializer(ObjectMapper objectMapper) { 47 | return new JacksonSerializer(objectMapper); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /trading-engine/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #spring.h2.console.enabled=true 2 | #spring.main.banner-mode=off 3 | #spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL55Dialect 4 | # 5 | management.endpoints.enabled-by-default=true 6 | management.endpoints.web.exposure.include=* 7 | management.cloudfoundry.enabled=true 8 | 9 | axon.distributed.enabled=true 10 | axon.distributed.spring-cloud.fallback-to-http-get=false 11 | 12 | axon.amqp.exchange=trading-engine-events 13 | axon.amqp.transaction-mode=publisher_ack 14 | 15 | axon.serializer.events=jackson 16 | 17 | spring.cloud.services.registrationMethod=direct 18 | 19 | # Open Session In View in combination with Server Sent Events is connection trouble waiting to happen 20 | spring.jpa.open-in-view=false 21 | -------------------------------------------------------------------------------- /trading-engine/src/main/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=trading-engine 2 | #spring.main.banner-mode=off 3 | #org.springframework.cloud.config=DEBUG 4 | 5 | 6 | spring.jpa.generate-ddl=true 7 | spring.jpa.hibernate.ddl-auto=create 8 | spring.jpa.show-sql=true 9 | 10 | #spring.jpa.database=mysql 11 | #spring.jpa.database=h2 12 | #spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect 13 | #spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect 14 | -------------------------------------------------------------------------------- /trading-engine/src/test/resources/bootstrap.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=trading-engine 2 | #spring.main.banner-mode=off 3 | 4 | management.security.enabled=false 5 | security.basic.enabled=false 6 | 7 | spring.thymeleaf.cache=false 8 | 9 | axon.amqp.exchange=CatalogEvents 10 | axon.eventhandling.processors.amqpEvents.source=complaintEventsMethod 11 | 12 | # Set the general logging level within the application 13 | logging.level.io.pivotal.refarch=DEBUG 14 | your.host.is=Me --------------------------------------------------------------------------------