├── .gitattributes ├── .github └── workflows │ ├── build.cqrs_flow.dotnet.yml │ ├── build.cqrs_flow.java.aggregates.yml │ ├── build.cqrs_flow.java.simple.yml │ └── build.crypto_shredding.dotnet.yml ├── .gitignore ├── CONTRIBUTING.md ├── CQRS_Flow ├── Dotnet │ ├── .editorconfig │ ├── Carts │ │ ├── Carts.Api.Tests │ │ │ ├── Carts.Api.Tests.csproj │ │ │ ├── Carts │ │ │ │ ├── AddingProduct │ │ │ │ │ └── AddProductTests.cs │ │ │ │ ├── Confirming │ │ │ │ │ └── ConfirmShoppingCartTests.cs │ │ │ │ ├── InitializingCart │ │ │ │ │ └── InitializeCartTests.cs │ │ │ │ └── RemovingProduct │ │ │ │ │ └── RemoveProductTests.cs │ │ │ └── Settings.cs │ │ ├── Carts.Api │ │ │ ├── Carts.Api.csproj │ │ │ ├── Controllers │ │ │ │ └── CartsController.cs │ │ │ ├── Program.cs │ │ │ ├── Properties │ │ │ │ └── launchSettings.json │ │ │ ├── Requests │ │ │ │ └── Carts │ │ │ │ │ ├── AddProductRequest.cs │ │ │ │ │ ├── InitializeCartRequest.cs │ │ │ │ │ ├── PricedProductItemRequest.cs │ │ │ │ │ ├── ProductItemRequest.cs │ │ │ │ │ └── RemoveProduct.cs │ │ │ ├── Startup.cs │ │ │ ├── appsettings.Development.json │ │ │ └── appsettings.json │ │ ├── Carts.Tests │ │ │ ├── Builders │ │ │ │ └── CartBuilder.cs │ │ │ ├── Carts.Tests.csproj │ │ │ ├── Carts │ │ │ │ ├── ConfirmingCart │ │ │ │ │ └── ConfirmCartTests.cs │ │ │ │ └── InitializingCart │ │ │ │ │ ├── InitializeCartCommandHandlerTests.cs │ │ │ │ │ └── InitializeCartTests.cs │ │ │ ├── Extensions │ │ │ │ └── Reservations │ │ │ │ │ └── CartExtensions.cs │ │ │ └── Stubs │ │ │ │ └── Products │ │ │ │ └── FakeProductPriceCalculator.cs │ │ └── Carts │ │ │ ├── Carts.csproj │ │ │ ├── Carts │ │ │ ├── AddingProduct │ │ │ │ ├── AddProduct.cs │ │ │ │ └── ProductAdded.cs │ │ │ ├── Cart.cs │ │ │ ├── CartStatus.cs │ │ │ ├── Config.cs │ │ │ ├── ConfirmingCart │ │ │ │ ├── CartConfirmed.cs │ │ │ │ └── ConfirmCart.cs │ │ │ ├── GettingCartAtVersion │ │ │ │ └── GetCartAtVersion.cs │ │ │ ├── GettingCartById │ │ │ │ ├── CartDetails.cs │ │ │ │ └── GetCartById.cs │ │ │ ├── GettingCartHistory │ │ │ │ ├── CartHistory.cs │ │ │ │ └── GetCartHistory.cs │ │ │ ├── GettingCarts │ │ │ │ ├── CartShortInfo.cs │ │ │ │ └── GetCarts.cs │ │ │ ├── InitializingCart │ │ │ │ ├── CartInitialized.cs │ │ │ │ └── InitializeCart.cs │ │ │ ├── Products │ │ │ │ ├── PricedProductItem.cs │ │ │ │ └── ProductItem.cs │ │ │ └── RemovingProduct │ │ │ │ ├── ProductRemoved.cs │ │ │ │ └── RemoveProduct.cs │ │ │ ├── Config.cs │ │ │ └── Pricing │ │ │ ├── IProductPriceCalculator.cs │ │ │ └── RandomProductPriceCalculator.cs │ ├── Core │ │ ├── Core.ElasticSearch │ │ │ ├── Config.cs │ │ │ ├── Core.ElasticSearch.csproj │ │ │ ├── Indices │ │ │ │ └── IndexNameMapper.cs │ │ │ ├── Projections │ │ │ │ └── ElasticSearchProjection.cs │ │ │ └── Repository │ │ │ │ └── ElasticSearchRepository.cs │ │ ├── Core.EventStoreDB │ │ │ ├── Config.cs │ │ │ ├── Core.EventStoreDB.csproj │ │ │ ├── Events │ │ │ │ ├── AggregateStreamExtensions.cs │ │ │ │ └── StreamEventExtensions.cs │ │ │ ├── Repository │ │ │ │ └── EventStoreDBRepository.cs │ │ │ ├── Serialization │ │ │ │ └── EventStoreDBSerializer.cs │ │ │ └── Subscriptions │ │ │ │ ├── EventStoreDBSubscriptionCheckpointRepository.cs │ │ │ │ ├── EventStoreDBSubscriptionToAll.cs │ │ │ │ ├── ISubscriptionCheckpointRepository.cs │ │ │ │ └── InMemorySubscriptionCheckpointRepository.cs │ │ ├── Core.Testing │ │ │ ├── AggregateExtensions.cs │ │ │ ├── ApiFixture.cs │ │ │ ├── Core.Testing.csproj │ │ │ ├── FakeIdGenerator.cs │ │ │ ├── FakeRepository.cs │ │ │ ├── ResponseExtensions.cs │ │ │ ├── SerializationExtensions.cs │ │ │ ├── TestContext.cs │ │ │ └── TestWebHostBuilder.cs │ │ ├── Core.WebApi │ │ │ ├── Core.WebApi.csproj │ │ │ └── Middlewares │ │ │ │ └── ExceptionHandling │ │ │ │ ├── ExceptionHandlingMiddleware.cs │ │ │ │ ├── ExceptionToHttpStatusMapper.cs │ │ │ │ └── HttpExceptionWrapper.cs │ │ └── Core │ │ │ ├── Aggregates │ │ │ ├── Aggregate.cs │ │ │ └── IAggregate.cs │ │ │ ├── BackgroundWorkers │ │ │ └── BackgroundWorker.cs │ │ │ ├── Commands │ │ │ ├── CommandBus.cs │ │ │ ├── Config.cs │ │ │ ├── ICommandBus.cs │ │ │ └── ICommandHandler.cs │ │ │ ├── Config.cs │ │ │ ├── Core.csproj │ │ │ ├── Events │ │ │ ├── Config.cs │ │ │ ├── EventBus.cs │ │ │ ├── EventTypeMapper.cs │ │ │ ├── IEventBus.cs │ │ │ ├── IEventHandler.cs │ │ │ ├── StreamEvent.cs │ │ │ └── StreamNameMapper.cs │ │ │ ├── Exceptions │ │ │ └── AggregateNotFoundException.cs │ │ │ ├── Extensions │ │ │ └── ListExtensions.cs │ │ │ ├── Ids │ │ │ ├── IIdGenerator.cs │ │ │ └── NulloIdGenerator.cs │ │ │ ├── Projections │ │ │ └── IProjection.cs │ │ │ ├── Queries │ │ │ ├── Config.cs │ │ │ ├── IQueryBus.cs │ │ │ ├── IQueryHandler.cs │ │ │ └── QueryBus.cs │ │ │ ├── Reflection │ │ │ └── TypeProvider.cs │ │ │ ├── Repositories │ │ │ ├── IRepository.cs │ │ │ └── RepositoryExtensions.cs │ │ │ └── Threading │ │ │ └── NoSynchronizationContextScope.cs │ ├── ECommerce.run.xml │ ├── ECommerce.sln │ ├── README.md │ └── docker-compose.yml └── Java │ ├── .gitignore │ ├── event-sourcing-esdb-aggregates │ ├── .editorconfig │ ├── .gitignore │ ├── README.md │ ├── build.gradle │ ├── docker-compose.yml │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src │ │ ├── main │ │ ├── java │ │ │ └── io │ │ │ │ └── eventdriven │ │ │ │ └── ecommerce │ │ │ │ ├── ECommerceApplication.java │ │ │ │ ├── api │ │ │ │ ├── backgroundworkers │ │ │ │ │ └── EventStoreDBSubscriptionBackgroundWorker.java │ │ │ │ ├── controller │ │ │ │ │ └── ShoppingCartsController.java │ │ │ │ └── requests │ │ │ │ │ └── ShoppingCartsRequests.java │ │ │ │ ├── core │ │ │ │ ├── aggregates │ │ │ │ │ ├── AbstractAggregate.java │ │ │ │ │ ├── Aggregate.java │ │ │ │ │ └── AggregateStore.java │ │ │ │ ├── config │ │ │ │ │ ├── CoreConfig.java │ │ │ │ │ └── EventStoreDBConfig.java │ │ │ │ ├── events │ │ │ │ │ ├── EventBus.java │ │ │ │ │ ├── EventEnvelope.java │ │ │ │ │ ├── EventForwarder.java │ │ │ │ │ ├── EventMetadata.java │ │ │ │ │ └── EventTypeMapper.java │ │ │ │ ├── http │ │ │ │ │ ├── ETag.java │ │ │ │ │ └── GlobalExceptionHandler.java │ │ │ │ ├── projections │ │ │ │ │ └── JPAProjection.java │ │ │ │ ├── serialization │ │ │ │ │ └── EventSerializer.java │ │ │ │ ├── subscriptions │ │ │ │ │ ├── CheckpointStored.java │ │ │ │ │ ├── EventStoreDBSubscriptionCheckpointRepository.java │ │ │ │ │ ├── EventStoreDBSubscriptionToAll.java │ │ │ │ │ ├── EventStoreDBSubscriptionToAllOptions.java │ │ │ │ │ └── SubscriptionCheckpointRepository.java │ │ │ │ └── views │ │ │ │ │ └── VersionedView.java │ │ │ │ ├── package-info.java │ │ │ │ ├── pricing │ │ │ │ ├── PricingConfig.java │ │ │ │ ├── ProductPriceCalculator.java │ │ │ │ └── RandomProductPriceCalculator.java │ │ │ │ └── shoppingcarts │ │ │ │ ├── ShoppingCart.java │ │ │ │ ├── ShoppingCartEvent.java │ │ │ │ ├── ShoppingCartService.java │ │ │ │ ├── ShoppingCartStatus.java │ │ │ │ ├── ShoppingCartsConfig.java │ │ │ │ ├── gettingbyid │ │ │ │ ├── GetShoppingCartById.java │ │ │ │ ├── ShoppingCartDetails.java │ │ │ │ ├── ShoppingCartDetailsProductItem.java │ │ │ │ ├── ShoppingCartDetailsProjection.java │ │ │ │ └── ShoppingCartDetailsRepository.java │ │ │ │ ├── gettingcarts │ │ │ │ ├── GetShoppingCarts.java │ │ │ │ ├── ShoppingCartShortInfo.java │ │ │ │ ├── ShoppingCartShortInfoProjection.java │ │ │ │ └── ShoppingCartShortInfoRepository.java │ │ │ │ └── productitems │ │ │ │ ├── PricedProductItem.java │ │ │ │ ├── ProductItem.java │ │ │ │ └── ProductItems.java │ │ └── resources │ │ │ ├── application.properties │ │ │ ├── log4j2.xml │ │ │ └── schema-postgres.sql │ │ └── test │ │ └── java │ │ └── io │ │ └── eventdriven │ │ └── ecommerce │ │ ├── api │ │ └── controller │ │ │ ├── AddProductItemToShoppingCartTests.java │ │ │ ├── CancelShoppingCartTests.java │ │ │ ├── ConfirmShoppingCartTests.java │ │ │ ├── OpenShoppingCartTests.java │ │ │ ├── RemoveProductItemFromShoppingCartTests.java │ │ │ └── builders │ │ │ └── ShoppingCartRestBuilder.java │ │ ├── shoppingcarts │ │ └── ShoppingCartTests.java │ │ └── testing │ │ ├── ApiSpecification.java │ │ └── HttpEntityUtils.java │ └── event-sourcing-esdb-simple │ ├── .editorconfig │ ├── .gitignore │ ├── README.md │ ├── build.gradle │ ├── docker-compose.yml │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle │ └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── eventdriven │ │ │ └── ecommerce │ │ │ ├── ECommerceApplication.java │ │ │ ├── api │ │ │ ├── backgroundworkers │ │ │ │ └── EventStoreDBSubscriptionBackgroundWorker.java │ │ │ ├── controller │ │ │ │ └── ShoppingCartsController.java │ │ │ └── requests │ │ │ │ └── ShoppingCartsRequests.java │ │ │ ├── core │ │ │ ├── config │ │ │ │ ├── CoreConfig.java │ │ │ │ └── EventStoreDBConfig.java │ │ │ ├── entities │ │ │ │ └── EntityStore.java │ │ │ ├── events │ │ │ │ ├── EventBus.java │ │ │ │ ├── EventEnvelope.java │ │ │ │ ├── EventForwarder.java │ │ │ │ ├── EventMetadata.java │ │ │ │ └── EventTypeMapper.java │ │ │ ├── http │ │ │ │ ├── ETag.java │ │ │ │ └── GlobalExceptionHandler.java │ │ │ ├── projections │ │ │ │ └── JPAProjection.java │ │ │ ├── serialization │ │ │ │ └── EventSerializer.java │ │ │ ├── subscriptions │ │ │ │ ├── CheckpointStored.java │ │ │ │ ├── EventStoreDBSubscriptionCheckpointRepository.java │ │ │ │ ├── EventStoreDBSubscriptionToAll.java │ │ │ │ ├── EventStoreDBSubscriptionToAllOptions.java │ │ │ │ └── SubscriptionCheckpointRepository.java │ │ │ └── views │ │ │ │ └── VersionedView.java │ │ │ ├── package-info.java │ │ │ ├── pricing │ │ │ ├── PricingConfig.java │ │ │ ├── ProductPriceCalculator.java │ │ │ └── RandomProductPriceCalculator.java │ │ │ └── shoppingcarts │ │ │ ├── ShoppingCart.java │ │ │ ├── ShoppingCartEvent.java │ │ │ ├── ShoppingCartService.java │ │ │ ├── ShoppingCartsConfig.java │ │ │ ├── addingproductitem │ │ │ └── AddProductItemToShoppingCart.java │ │ │ ├── canceling │ │ │ └── CancelShoppingCart.java │ │ │ ├── confirming │ │ │ └── ConfirmShoppingCart.java │ │ │ ├── gettingbyid │ │ │ ├── GetShoppingCartById.java │ │ │ ├── ShoppingCartDetails.java │ │ │ ├── ShoppingCartDetailsProductItem.java │ │ │ ├── ShoppingCartDetailsProjection.java │ │ │ └── ShoppingCartDetailsRepository.java │ │ │ ├── gettingcarts │ │ │ ├── GetShoppingCarts.java │ │ │ ├── ShoppingCartShortInfo.java │ │ │ ├── ShoppingCartShortInfoProjection.java │ │ │ └── ShoppingCartShortInfoRepository.java │ │ │ ├── opening │ │ │ └── OpenShoppingCart.java │ │ │ ├── productitems │ │ │ ├── PricedProductItem.java │ │ │ ├── ProductItem.java │ │ │ └── ProductItems.java │ │ │ └── removingproductitem │ │ │ └── RemoveProductItemFromShoppingCart.java │ └── resources │ │ ├── application.properties │ │ ├── log4j2.xml │ │ └── schema-postgres.sql │ └── test │ └── java │ └── io │ └── eventdriven │ └── ecommerce │ ├── api │ └── controller │ │ ├── AddProductItemToShoppingCartTests.java │ │ ├── CancelShoppingCartTests.java │ │ ├── ConfirmShoppingCartTests.java │ │ ├── OpenShoppingCartTests.java │ │ ├── RemoveProductItemFromShoppingCartTests.java │ │ └── builders │ │ └── ShoppingCartRestBuilder.java │ ├── shoppingcarts │ └── ShoppingCartTests.java │ └── testing │ ├── ApiSpecification.java │ └── HttpEntityUtils.java ├── Crypto_Shredding └── Dotnet │ ├── .gitignore │ ├── CryptoShredding.sln │ ├── CryptoShredding.sln.DotSettings │ ├── README.md │ ├── docker-compose.yml │ └── src │ ├── CryptoShredding.IntegrationTests │ ├── CryptoShredding.IntegrationTests.csproj │ ├── EventStoreTests │ │ └── GetEventsTests.cs │ └── TestSupport │ │ └── Given_When_Then.cs │ └── CryptoShredding │ ├── Attributes │ ├── DataSubjectIdAttribute.cs │ └── PersonalDataAttribute.cs │ ├── Contracts │ └── IEvent.cs │ ├── CryptoShredding.csproj │ ├── EventConverter.cs │ ├── EventStore.cs │ ├── Repository │ ├── CryptoRepository.cs │ └── EncryptionKey.cs │ └── Serialization │ ├── ContractResolvers │ ├── DeserializationContractResolver.cs │ └── SerializationContractResolver.cs │ ├── EncryptorDecryptor.cs │ ├── JsonConverters │ ├── DecryptionJsonConverter.cs │ ├── EncryptionJsonConverter.cs │ └── FieldEncryptionDecryption.cs │ ├── JsonSerializer.cs │ ├── JsonSerializerSettingsFactory.cs │ └── SerializedEvent.cs ├── LICENSE ├── LoanApplication ├── Python │ ├── CreditCheck.py │ ├── LoanDecider.py │ ├── LoanRequestor-commandLine.py │ ├── LoanRequestor-testCases.py │ ├── Projection-LoansApprovedDenied.js │ ├── Projection-LoansByCountryName.js │ ├── README.md │ ├── Underwriting.py │ ├── config.py │ ├── create_projections.sh │ └── utils.py ├── README.md ├── docker-compose.yaml └── images │ ├── image1.png │ ├── image10x.png │ ├── image11.png │ ├── image11x.png │ ├── image12x.png │ ├── image13.png │ ├── image14.png │ ├── image15.png │ ├── image16.png │ ├── image17.png │ ├── image18.png │ ├── image2.png │ ├── image3.png │ ├── image4.png │ ├── image5.png │ ├── image6x.png │ ├── image7x.png │ ├── image8x.png │ └── image9x.png ├── Logging └── Elastic │ ├── Filebeat │ ├── README.md │ ├── docker-compose.yml │ ├── filebeat.yml │ └── logs │ │ └── .gitignore │ ├── FilebeatWithLogstash │ ├── README.md │ ├── docker-compose.yml │ ├── filebeat.yml │ ├── logs │ │ └── .gitignore │ └── logstash.conf │ ├── Logstash │ ├── README.md │ ├── docker-compose.yml │ ├── logs │ │ └── .gitignore │ └── logstash.conf │ └── README.md ├── Quickstart ├── Dotnet │ └── esdb-sample-dotnet │ │ ├── .dockerignore │ │ ├── Dockerfile │ │ ├── EventStoreDB.Dotnet.Sample.csproj │ │ ├── EventStoreDB.Dotnet.Sample.sln │ │ ├── Program.cs │ │ ├── Properties │ │ └── launchSettings.json │ │ ├── README.md │ │ ├── appsettings.json │ │ └── docker-compose.yml ├── Go │ └── esdb-sample-go │ │ ├── .dockerignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── docker-compose.yml │ │ ├── go.mod │ │ ├── go.sum │ │ └── main.go ├── Java │ └── esdb-sample-springboot │ │ ├── .dockerignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── build.gradle │ │ ├── docker-compose.yml │ │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ ├── mvnw.cmd │ │ ├── mvnw.txt │ │ ├── pom.xml │ │ ├── settings.gradle │ │ └── src │ │ └── main │ │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── esdbsamplespringboot │ │ │ ├── EventStoreDBConfiguration.java │ │ │ ├── HelloWorldApplication.java │ │ │ └── HelloWorldController.java │ │ └── resources │ │ ├── application.properties │ │ └── log4j.properties ├── Nodejs │ └── esdb-sample-nodejs │ │ ├── .dockerignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── app.js │ │ ├── docker-compose.yml │ │ ├── package-lock.json │ │ └── package.json ├── Python │ └── esdb-sample-python │ │ ├── .dockerignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── docker-compose.yml │ │ ├── main.py │ │ └── requirements.txt └── Rust │ └── esdb-sample-rust │ ├── .dockerignore │ ├── Cargo.lock │ ├── Cargo.toml │ ├── Dockerfile │ ├── README.md │ ├── Rocket.toml │ ├── docker-compose.yml │ └── src │ └── main.rs └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | *.png binary 3 | *.ttf binary 4 | *.jpg binary 5 | *.jpeg binary 6 | *.pdf binary -------------------------------------------------------------------------------- /.github/workflows/build.cqrs_flow.dotnet.yml: -------------------------------------------------------------------------------- 1 | name: Build CQRS Flow .NET 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | defaults: 10 | run: 11 | working-directory: ./CQRS_Flow/Dotnet/ 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Check Out Repo 19 | uses: actions/checkout@v1 20 | 21 | - name: Start containers 22 | run: docker-compose -f "docker-compose.yml" up -d 23 | 24 | - name: Setup .NET Core 25 | uses: actions/setup-dotnet@v1 26 | with: 27 | dotnet-version: "6.0.x" 28 | 29 | - name: Restore NuGet packages 30 | run: dotnet restore 31 | 32 | - name: Build 33 | run: dotnet build --configuration Release --no-restore 34 | 35 | - name: Run tests 36 | run: dotnet test --configuration Release --no-build --logger "trx;LogFileName=test-results.trx" 37 | 38 | - name: Stop containers 39 | if: always() 40 | run: docker-compose -f "docker-compose.yml" down 41 | -------------------------------------------------------------------------------- /.github/workflows/build.cqrs_flow.java.aggregates.yml: -------------------------------------------------------------------------------- 1 | name: Build CQRS Flow Java - Aggregates 2 | 3 | on: 4 | # run it on push to the default repository branch 5 | push: 6 | branches: [main] 7 | # run it during pull request 8 | pull_request: 9 | 10 | defaults: 11 | run: 12 | working-directory: ./CQRS_Flow/Java/event-sourcing-esdb-aggregates 13 | 14 | jobs: 15 | build-and-test-code: 16 | name: Build and test 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: Check Out Repo 21 | uses: actions/checkout@v2 22 | 23 | - name: Start containers 24 | run: docker-compose up -d 25 | 26 | - name: Set up JDK 17 27 | uses: actions/setup-java@v2 28 | with: 29 | java-version: 17 30 | distribution: "adopt" 31 | cache: gradle 32 | 33 | - uses: gradle/gradle-build-action@v2 34 | with: 35 | arguments: build 36 | gradle-version: wrapper 37 | build-root-directory: ./CQRS_Flow/Java/event-sourcing-esdb-aggregates 38 | 39 | - name: Archive test report 40 | uses: actions/upload-artifact@v2 41 | if: always() 42 | with: 43 | name: Test report 44 | path: ./CQRS_Flow/Java/event-sourcing-esdb-aggregates/build/test-results/test 45 | 46 | - name: Stop containers 47 | if: always() 48 | run: docker-compose down 49 | -------------------------------------------------------------------------------- /.github/workflows/build.cqrs_flow.java.simple.yml: -------------------------------------------------------------------------------- 1 | name: Build CQRS Flow Java - Simple 2 | 3 | on: 4 | # run it on push to the default repository branch 5 | push: 6 | branches: [main] 7 | # run it during pull request 8 | pull_request: 9 | 10 | defaults: 11 | run: 12 | working-directory: ./CQRS_Flow/Java/event-sourcing-esdb-simple 13 | 14 | jobs: 15 | build-and-test-code: 16 | name: Build and test 17 | runs-on: ubuntu-latest 18 | 19 | steps: 20 | - name: Check Out Repo 21 | uses: actions/checkout@v2 22 | 23 | - name: Start containers 24 | run: docker-compose up -d 25 | 26 | - name: Set up JDK 17 27 | uses: actions/setup-java@v2 28 | with: 29 | java-version: 17 30 | distribution: "adopt" 31 | cache: gradle 32 | 33 | - uses: gradle/gradle-build-action@v2 34 | with: 35 | arguments: build 36 | gradle-version: wrapper 37 | build-root-directory: ./CQRS_Flow/Java/event-sourcing-esdb-simple 38 | 39 | - name: Archive test report 40 | uses: actions/upload-artifact@v2 41 | if: always() 42 | with: 43 | name: Test report 44 | path: ./CQRS_Flow/Java/event-sourcing-esdb-simple/build/test-results/test 45 | 46 | - name: Stop containers 47 | if: always() 48 | run: docker-compose down 49 | -------------------------------------------------------------------------------- /.github/workflows/build.crypto_shredding.dotnet.yml: -------------------------------------------------------------------------------- 1 | name: Build Crypto Shredding .NET 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | 9 | defaults: 10 | run: 11 | working-directory: ./Crypto_Shredding/Dotnet 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Check Out Repo 19 | uses: actions/checkout@v1 20 | 21 | - name: Start containers 22 | run: docker-compose -f "docker-compose.yml" up -d 23 | 24 | - name: Setup .NET Core 25 | uses: actions/setup-dotnet@v1 26 | with: 27 | dotnet-version: "6.0.x" 28 | 29 | - name: Restore NuGet packages 30 | run: dotnet restore 31 | 32 | - name: Build 33 | run: dotnet build --configuration Release --no-restore 34 | 35 | - name: Run tests 36 | run: dotnet test --configuration Release --no-build --logger "trx;LogFileName=test-results.trx" 37 | 38 | - name: Stop containers 39 | if: always() 40 | run: docker-compose -f "docker-compose.yml" down 41 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to EventStoreDB samples 2 | 3 | ## Before you submit a Pull Request (PR) 4 | 5 | 1. Contact us via a [GitHub Issue](https://github.com/EventStore/samples/issues/new) and please include a proposed solution with details 6 | 2. Make sure your code runs as intended and all tests pass 7 | 8 | ## Once you've submitted a PR 9 | 10 | 1. Your PR will be merged once approved by at least 2 reviewers. 11 | 12 | ## Working with Git 13 | 14 | `main` is just that: the main branch. Releases are tagged from main. 15 | 16 | We attempt to do our best to ensure that the history is preserved and to do so, we generally ask contributors to squash their commits into a single logical commit. 17 | 18 | To contribute to EventStoreDB samples: 19 | 20 | 1. Fork the repository 21 | 2. Create a feature branch from the `main` (or release) branch 22 | 3. We recommend you use a rebase strategy for feature branches (see more in [Git documentation](https://git-scm.com/book/en/v2/Git-Branching-Rebasing)). Please use clear commit messages. Commits should also represent a single unit of change 23 | 4. Before sending a PR, please make sure you got the latest source branch from the main repository 24 | 5. When ready, create a [Pull Request on GitHub](https://github.com/EventStore/samples/compare) 25 | 26 | ## Code style 27 | 28 | Coding rules are set up in the project files to be automatically applied (e.g. with `.editorconfig` or `.prettierrc.json`). Unless you disabled it manually, it should be automatically applied by your IDE after opening the project. We also recommend turning automatic formatting on saving so all rules are applied. 29 | 30 | ## Licensing and legal rights 31 | 32 | By contributing to EventStoreDB: 33 | 34 | 1. You assert that the contribution is your original work. 35 | 2. You assert that you have the right to assign the copyright for the work. 36 | 3. You accept the [License](LICENSE.md). 37 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Api.Tests/Carts.Api.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Api.Tests/Settings.cs: -------------------------------------------------------------------------------- 1 | using Xunit; 2 | 3 | [assembly: CollectionBehavior(DisableTestParallelization = true)] 4 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Api/Carts.Api.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Api/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace Carts.Api; 6 | 7 | public class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | CreateHostBuilder(args).Build().Run(); 12 | } 13 | 14 | public static IHostBuilder CreateHostBuilder(string[] args) => 15 | Host.CreateDefaultBuilder(args) 16 | .ConfigureWebHostDefaults(webBuilder => 17 | { 18 | webBuilder.UseStartup(); 19 | }) 20 | .ConfigureLogging(logging => 21 | { 22 | logging.ClearProviders(); 23 | logging.AddConsole(); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:38471", 7 | "sslPort": 44357 8 | } 9 | }, 10 | "profiles": { 11 | "CartsApi": { 12 | "commandName": "Project", 13 | "launchBrowser": true, 14 | "launchUrl": "http://localhost:5500", 15 | "environmentVariables": { 16 | "ASPNETCORE_ENVIRONMENT": "Development" 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Api/Requests/Carts/AddProductRequest.cs: -------------------------------------------------------------------------------- 1 | namespace Carts.Api.Requests.Carts; 2 | 3 | public record AddProductRequest( 4 | ProductItemRequest? ProductItem 5 | ); 6 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Api/Requests/Carts/InitializeCartRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Carts.Api.Requests.Carts; 4 | 5 | public record InitializeCartRequest( 6 | Guid? ClientId 7 | ); -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Api/Requests/Carts/PricedProductItemRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Carts.Api.Requests.Carts; 4 | 5 | public record PricedProductItemRequest( 6 | Guid? ProductId, 7 | int? Quantity, 8 | decimal? UnitPrice 9 | ); -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Api/Requests/Carts/ProductItemRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Carts.Api.Requests.Carts; 4 | 5 | public record ProductItemRequest( 6 | Guid? ProductId, 7 | int? Quantity 8 | ); -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Api/Requests/Carts/RemoveProduct.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Carts.Api.Requests.Carts; 4 | 5 | public record RemoveProductRequest( 6 | Guid? CartId, 7 | PricedProductItemRequest? ProductItem 8 | ); -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Api/Startup.cs: -------------------------------------------------------------------------------- 1 | using Core; 2 | using Core.EventStoreDB; 3 | using Core.WebApi.Middlewares.ExceptionHandling; 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Hosting; 9 | using Microsoft.OpenApi.Models; 10 | using Newtonsoft.Json.Converters; 11 | 12 | namespace Carts.Api; 13 | 14 | public class Startup 15 | { 16 | private readonly IConfiguration config; 17 | 18 | public Startup(IConfiguration config) 19 | { 20 | this.config = config; 21 | } 22 | 23 | public void ConfigureServices(IServiceCollection services) 24 | { 25 | services.AddMvc() 26 | .AddNewtonsoftJson(opt => opt.SerializerSettings.Converters.Add(new StringEnumConverter())); 27 | 28 | services.AddControllers(); 29 | 30 | services.AddSwaggerGen(c => 31 | { 32 | c.SwaggerDoc("v1", new OpenApiInfo {Title = "Carts", Version = "v1"}); 33 | }); 34 | 35 | services 36 | .AddCoreServices() 37 | .AddEventStoreDBSubscriptionToAll() 38 | .AddCartsModule(config); 39 | } 40 | 41 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 42 | { 43 | if (env.IsDevelopment()) 44 | { 45 | app.UseDeveloperExceptionPage(); 46 | } 47 | 48 | app.UseMiddleware(typeof(ExceptionHandlingMiddleware)); 49 | 50 | app.UseRouting(); 51 | 52 | app.UseAuthorization(); 53 | 54 | app.UseEndpoints(endpoints => 55 | { 56 | endpoints.MapControllers(); 57 | }); 58 | 59 | app.UseSwagger(); 60 | 61 | app.UseSwaggerUI(c => 62 | { 63 | c.SwaggerEndpoint("/swagger/v1/swagger.json", "Carts V1"); 64 | c.RoutePrefix = string.Empty; 65 | }); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Api/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | }, 10 | "AllowedHosts": "*", 11 | "EventStore": { 12 | "ConnectionString": "esdb://localhost:2113?tls=false" 13 | }, 14 | "Elasticsearch": { 15 | "DefaultIndex": "carts-default", 16 | "Url": "http://localhost:9200/" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Tests/Builders/CartBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Carts.Carts; 4 | using Carts.Carts.InitializingCart; 5 | 6 | namespace Carts.Tests.Builders; 7 | 8 | internal class CartBuilder 9 | { 10 | private readonly Queue eventsToApply = new(); 11 | 12 | public CartBuilder Initialized() 13 | { 14 | var cartId = Guid.NewGuid(); 15 | var clientId = Guid.NewGuid(); 16 | 17 | eventsToApply.Enqueue(new CartInitialized(cartId, clientId, CartStatus.Pending)); 18 | 19 | return this; 20 | } 21 | 22 | public static CartBuilder Create() => new(); 23 | 24 | public Cart Build() 25 | { 26 | var cart = (Cart) Activator.CreateInstance(typeof(Cart), true)!; 27 | 28 | foreach (var @event in eventsToApply) 29 | { 30 | cart.When(@event); 31 | } 32 | 33 | return cart; 34 | } 35 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Tests/Carts.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Tests/Carts/ConfirmingCart/ConfirmCartTests.cs: -------------------------------------------------------------------------------- 1 | using Carts.Carts; 2 | using Carts.Carts.ConfirmingCart; 3 | using Carts.Tests.Builders; 4 | using Core.Testing; 5 | using FluentAssertions; 6 | using Xunit; 7 | 8 | namespace Carts.Tests.Carts.ConfirmingCart; 9 | 10 | public class ConfirmCartTests 11 | { 12 | [Fact] 13 | public void ForTentativeCart_ShouldSucceed() 14 | { 15 | // Given 16 | var cart = CartBuilder 17 | .Create() 18 | .Initialized() 19 | .Build(); 20 | 21 | // When 22 | cart.Confirm(); 23 | 24 | // Then 25 | cart.Status.Should().Be(CartStatus.Confirmed); 26 | cart.Version.Should().Be(2); 27 | 28 | var @event = cart.PublishedEvent(); 29 | 30 | @event.Should().NotBeNull(); 31 | @event.Should().BeOfType(); 32 | @event!.CartId.Should().Be(cart.Id); 33 | } 34 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Tests/Carts/InitializingCart/InitializeCartCommandHandlerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Carts.Carts; 6 | using Carts.Carts.InitializingCart; 7 | using Carts.Tests.Extensions.Reservations; 8 | using Core.Testing; 9 | using FluentAssertions; 10 | using Xunit; 11 | 12 | namespace Carts.Tests.Carts.InitializingCart; 13 | 14 | public class InitializeCartCommandHandlerTests 15 | { 16 | [Fact] 17 | public async Task ForInitCardCommand_ShouldAddNewCart() 18 | { 19 | // Given 20 | var repository = new FakeRepository(); 21 | 22 | var commandHandler = new HandleInitializeCart( 23 | repository 24 | ); 25 | 26 | var command = InitializeCart.Create(Guid.NewGuid(), Guid.NewGuid()); 27 | 28 | // When 29 | await commandHandler.Handle(command, CancellationToken.None); 30 | 31 | //Then 32 | repository.Aggregates.Should().HaveCount(1); 33 | 34 | var cart = repository.Aggregates.Values.Single(); 35 | 36 | cart 37 | .IsInitializedCartWith( 38 | command.CartId, 39 | command.ClientId 40 | ) 41 | .HasCartInitializedEventWith( 42 | command.CartId, 43 | command.ClientId 44 | ); 45 | } 46 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Tests/Carts/InitializingCart/InitializeCartTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Carts.Carts; 3 | using Carts.Tests.Extensions.Reservations; 4 | using Xunit; 5 | 6 | namespace Carts.Tests.Carts.InitializingCart; 7 | 8 | public class InitializeCartTests 9 | { 10 | [Fact] 11 | public void ForValidParams_ShouldCreateCartWithPendingStatus() 12 | { 13 | // Given 14 | var cartId = Guid.NewGuid(); 15 | var clientId = Guid.NewGuid(); 16 | 17 | // When 18 | var cart = Cart.Initialize( 19 | cartId, 20 | clientId 21 | ); 22 | 23 | // Then 24 | 25 | cart 26 | .IsInitializedCartWith( 27 | cartId, 28 | clientId 29 | ) 30 | .HasCartInitializedEventWith( 31 | cartId, 32 | clientId 33 | ); 34 | } 35 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Tests/Extensions/Reservations/CartExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Carts.Carts; 3 | using Carts.Carts.InitializingCart; 4 | using Core.Testing; 5 | using FluentAssertions; 6 | 7 | namespace Carts.Tests.Extensions.Reservations; 8 | 9 | internal static class CartExtensions 10 | { 11 | public static Cart IsInitializedCartWith( 12 | this Cart cart, 13 | Guid id, 14 | Guid clientId) 15 | { 16 | 17 | cart.Id.Should().Be(id); 18 | cart.ClientId.Should().Be(clientId); 19 | cart.Status.Should().Be(CartStatus.Pending); 20 | cart.ProductItems.Should().BeEmpty(); 21 | cart.TotalPrice.Should().Be(0); 22 | cart.Version.Should().Be(1); 23 | 24 | return cart; 25 | } 26 | 27 | public static Cart HasCartInitializedEventWith( 28 | this Cart cart, 29 | Guid id, 30 | Guid clientId) 31 | { 32 | var @event = cart.PublishedEvent(); 33 | 34 | @event.Should().NotBeNull(); 35 | @event.Should().BeOfType(); 36 | @event!.CartId.Should().Be(id); 37 | @event.ClientId.Should().Be(clientId); 38 | @event.CartStatus.Should().Be(CartStatus.Pending); 39 | 40 | return cart; 41 | } 42 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts.Tests/Stubs/Products/FakeProductPriceCalculator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Carts.Carts.Products; 4 | using Carts.Pricing; 5 | 6 | namespace Carts.Tests.Stubs.Products; 7 | 8 | internal class FakeProductPriceCalculator: IProductPriceCalculator 9 | { 10 | public const decimal FakePrice = 13; 11 | public IReadOnlyList Calculate(params ProductItem[] productItems) 12 | { 13 | return productItems 14 | .Select(pi => 15 | PricedProductItem.Create(pi, FakePrice)) 16 | .ToList(); 17 | } 18 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts/Carts.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | <_Parameter1>$(AssemblyName).Tests 22 | 23 | 24 | <_Parameter1>$(AssemblyName).Api.Tests 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts/Carts/AddingProduct/AddProduct.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Carts.Carts.Products; 5 | using Carts.Pricing; 6 | using Core.Commands; 7 | using Core.Repositories; 8 | 9 | namespace Carts.Carts.AddingProduct; 10 | 11 | public class AddProduct 12 | { 13 | public Guid CartId { get; } 14 | 15 | public ProductItem ProductItem { get; } 16 | 17 | private AddProduct(Guid cartId, ProductItem productItem) 18 | { 19 | CartId = cartId; 20 | ProductItem = productItem; 21 | } 22 | public static AddProduct Create(Guid cartId, ProductItem productItem) 23 | { 24 | if (cartId == Guid.Empty) 25 | throw new ArgumentOutOfRangeException(nameof(cartId)); 26 | 27 | return new AddProduct(cartId, productItem); 28 | } 29 | } 30 | 31 | internal class HandleAddProduct: 32 | ICommandHandler 33 | { 34 | private readonly IRepository cartRepository; 35 | private readonly IProductPriceCalculator productPriceCalculator; 36 | 37 | public HandleAddProduct( 38 | IRepository cartRepository, 39 | IProductPriceCalculator productPriceCalculator 40 | ) 41 | { 42 | this.cartRepository = cartRepository; 43 | this.productPriceCalculator = productPriceCalculator; 44 | } 45 | 46 | public Task Handle(AddProduct command, CancellationToken cancellationToken) 47 | { 48 | return cartRepository.GetAndUpdate( 49 | command.CartId, 50 | cart => cart.AddProduct(productPriceCalculator, command.ProductItem), 51 | cancellationToken); 52 | } 53 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts/Carts/AddingProduct/ProductAdded.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Carts.Carts.Products; 3 | 4 | namespace Carts.Carts.AddingProduct; 5 | 6 | public class ProductAdded 7 | { 8 | public Guid CartId { get; } 9 | 10 | public PricedProductItem ProductItem { get; } 11 | 12 | public ProductAdded(Guid cartId, PricedProductItem productItem) 13 | { 14 | CartId = cartId; 15 | ProductItem = productItem; 16 | } 17 | 18 | public static ProductAdded Create(Guid cartId, PricedProductItem productItem) 19 | { 20 | if (cartId == Guid.Empty) 21 | throw new ArgumentOutOfRangeException(nameof(cartId)); 22 | 23 | return new ProductAdded(cartId, productItem); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts/Carts/CartStatus.cs: -------------------------------------------------------------------------------- 1 | namespace Carts.Carts; 2 | 3 | public enum CartStatus 4 | { 5 | Pending = 1, 6 | Confirmed = 2, 7 | Cancelled = 3 8 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts/Carts/ConfirmingCart/CartConfirmed.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | 4 | namespace Carts.Carts.ConfirmingCart; 5 | 6 | public class CartConfirmed 7 | { 8 | public Guid CartId { get; } 9 | 10 | public DateTime ConfirmedAt { get; } 11 | 12 | public CartConfirmed(Guid cartId, DateTime confirmedAt) 13 | { 14 | CartId = cartId; 15 | ConfirmedAt = confirmedAt; 16 | } 17 | 18 | public static CartConfirmed Create(Guid cartId, DateTime confirmedAt) 19 | { 20 | if (cartId == Guid.Empty) 21 | throw new ArgumentOutOfRangeException(nameof(cartId)); 22 | 23 | if(confirmedAt == DateTime.MinValue || confirmedAt == DateTime.MaxValue) 24 | throw new ArgumentOutOfRangeException(nameof(confirmedAt)); 25 | 26 | return new CartConfirmed(cartId, confirmedAt); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts/Carts/ConfirmingCart/ConfirmCart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Core.Commands; 5 | using Core.Repositories; 6 | 7 | namespace Carts.Carts.ConfirmingCart; 8 | 9 | public class ConfirmCart 10 | { 11 | public Guid CartId { get; } 12 | 13 | private ConfirmCart(Guid cartId) 14 | { 15 | CartId = cartId; 16 | } 17 | 18 | public static ConfirmCart Create(Guid cartId) 19 | { 20 | if (cartId == Guid.Empty) 21 | throw new ArgumentOutOfRangeException(nameof(cartId)); 22 | 23 | return new ConfirmCart(cartId); 24 | } 25 | } 26 | 27 | internal class HandleConfirmCart: 28 | ICommandHandler 29 | { 30 | private readonly IRepository cartRepository; 31 | 32 | public HandleConfirmCart( 33 | IRepository cartRepository 34 | ) 35 | { 36 | this.cartRepository = cartRepository; 37 | } 38 | 39 | public Task Handle(ConfirmCart command, CancellationToken cancellationToken) 40 | { 41 | return cartRepository.GetAndUpdate( 42 | command.CartId, 43 | cart => cart.Confirm(), 44 | cancellationToken); 45 | } 46 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts/Carts/GettingCartAtVersion/GetCartAtVersion.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Carts.Carts.GettingCartById; 5 | using Core.EventStoreDB.Events; 6 | using Core.Exceptions; 7 | using Core.Queries; 8 | using EventStore.Client; 9 | 10 | namespace Carts.Carts.GettingCartAtVersion; 11 | 12 | public class GetCartAtVersion 13 | { 14 | public Guid CartId { get; } 15 | public ulong Version { get; } 16 | 17 | private GetCartAtVersion(Guid cartId, ulong version) 18 | { 19 | CartId = cartId; 20 | Version = version; 21 | } 22 | 23 | public static GetCartAtVersion Create(Guid cartId, ulong version) 24 | { 25 | if (cartId == Guid.Empty) 26 | throw new ArgumentOutOfRangeException(nameof(cartId)); 27 | 28 | return new GetCartAtVersion(cartId, version); 29 | } 30 | } 31 | 32 | internal class HandleGetCartAtVersion : 33 | IQueryHandler 34 | { 35 | private readonly EventStoreClient eventStore; 36 | 37 | public HandleGetCartAtVersion(EventStoreClient eventStore) 38 | { 39 | this.eventStore = eventStore; 40 | } 41 | 42 | public async Task Handle(GetCartAtVersion request, CancellationToken cancellationToken) 43 | { 44 | var cart = await eventStore.AggregateStream( 45 | request.CartId, 46 | cancellationToken, 47 | request.Version 48 | ); 49 | 50 | if (cart == null) 51 | throw AggregateNotFoundException.For(request.CartId); 52 | 53 | return cart; 54 | } 55 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts/Carts/GettingCartById/GetCartById.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Core.ElasticSearch.Indices; 5 | using Core.Exceptions; 6 | using Core.Queries; 7 | using Nest; 8 | 9 | namespace Carts.Carts.GettingCartById; 10 | 11 | public class GetCartById 12 | { 13 | public Guid CartId { get; } 14 | 15 | private GetCartById(Guid cartId) 16 | { 17 | CartId = cartId; 18 | } 19 | 20 | public static GetCartById Create(Guid cartId) 21 | { 22 | if (cartId == Guid.Empty) 23 | throw new ArgumentOutOfRangeException(nameof(cartId)); 24 | 25 | return new GetCartById(cartId); 26 | } 27 | } 28 | 29 | internal class HandleGetCartById : 30 | IQueryHandler 31 | { 32 | private readonly IElasticClient elasticClient; 33 | 34 | public HandleGetCartById(IElasticClient elasticClient) 35 | { 36 | this.elasticClient = elasticClient; 37 | } 38 | 39 | public async Task Handle(GetCartById request, CancellationToken cancellationToken) 40 | { 41 | var result = await elasticClient.GetAsync(request.CartId, 42 | c => c.Index(IndexNameMapper.ToIndexName()), 43 | cancellationToken); 44 | 45 | return result?.Source ?? throw AggregateNotFoundException.For(request.CartId); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts/Carts/GettingCartHistory/GetCartHistory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Core.Queries; 7 | using Nest; 8 | 9 | namespace Carts.Carts.GettingCartHistory; 10 | 11 | public class GetCartHistory 12 | { 13 | public Guid CartId { get; } 14 | public int PageNumber { get; } 15 | public int PageSize { get; } 16 | 17 | private GetCartHistory(Guid cartId, int pageNumber, int pageSize) 18 | { 19 | CartId = cartId; 20 | PageNumber = pageNumber; 21 | PageSize = pageSize; 22 | } 23 | 24 | public static GetCartHistory Create(Guid cartId, int pageNumber = 1, int pageSize = 20) 25 | { 26 | if (pageNumber <= 0) 27 | throw new ArgumentOutOfRangeException(nameof(pageNumber)); 28 | if (pageSize is <= 0 or > 100) 29 | throw new ArgumentOutOfRangeException(nameof(pageSize)); 30 | 31 | return new GetCartHistory(cartId, pageNumber, pageSize); 32 | } 33 | } 34 | 35 | internal class HandleGetCartHistory: 36 | IQueryHandler> 37 | { 38 | private readonly IElasticClient elasticClient; 39 | 40 | public HandleGetCartHistory(IElasticClient elasticClient) 41 | { 42 | this.elasticClient = elasticClient; 43 | } 44 | 45 | public async Task> Handle(GetCartHistory request, 46 | CancellationToken cancellationToken) 47 | { 48 | var result = await elasticClient.SearchAsync( 49 | s => s 50 | .Query( 51 | q => q.Term(x => x.CartId, request.CartId) 52 | ) 53 | .Skip(request.PageNumber * request.PageSize) 54 | .Take(request.PageSize), 55 | cancellationToken); 56 | 57 | return result.Documents.ToList(); 58 | } 59 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts/Carts/GettingCarts/CartShortInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Carts.Carts.AddingProduct; 3 | using Carts.Carts.ConfirmingCart; 4 | using Carts.Carts.InitializingCart; 5 | using Carts.Carts.RemovingProduct; 6 | using Core.Projections; 7 | 8 | namespace Carts.Carts.GettingCarts; 9 | 10 | public class CartShortInfo: IProjection 11 | { 12 | public Guid Id { get; set; } 13 | 14 | public int TotalItemsCount { get; set; } 15 | 16 | public CartStatus Status { get; set; } 17 | 18 | public void When(object @event) 19 | { 20 | switch (@event) 21 | { 22 | case CartInitialized cartInitialized: 23 | Apply(cartInitialized); 24 | return; 25 | case ProductAdded cartInitialized: 26 | Apply(cartInitialized); 27 | return; 28 | case ProductRemoved cartInitialized: 29 | Apply(cartInitialized); 30 | return; 31 | case CartConfirmed cartInitialized: 32 | Apply(cartInitialized); 33 | return; 34 | } 35 | } 36 | 37 | public void Apply(CartInitialized @event) 38 | { 39 | Id = @event.CartId; 40 | TotalItemsCount = 0; 41 | Status = CartStatus.Pending; 42 | } 43 | 44 | public void Apply(ProductAdded @event) 45 | { 46 | TotalItemsCount += @event.ProductItem.Quantity; 47 | } 48 | 49 | public void Apply(ProductRemoved @event) 50 | { 51 | TotalItemsCount -= @event.ProductItem.Quantity; 52 | } 53 | 54 | public void Apply(CartConfirmed @event) 55 | { 56 | Status = CartStatus.Confirmed; 57 | } 58 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts/Carts/GettingCarts/GetCarts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Core.Queries; 7 | using Nest; 8 | 9 | namespace Carts.Carts.GettingCarts; 10 | 11 | public class GetCarts 12 | { 13 | public int PageNumber { get; } 14 | public int PageSize { get; } 15 | 16 | private GetCarts(int pageNumber, int pageSize) 17 | { 18 | PageNumber = pageNumber; 19 | PageSize = pageSize; 20 | } 21 | 22 | public static GetCarts Create(int pageNumber = 1, int pageSize = 20) 23 | { 24 | if (pageNumber <= 0) 25 | throw new ArgumentOutOfRangeException(nameof(pageNumber)); 26 | if (pageSize is <= 0 or > 100) 27 | throw new ArgumentOutOfRangeException(nameof(pageSize)); 28 | 29 | return new GetCarts(pageNumber, pageSize); 30 | } 31 | } 32 | 33 | internal class HandleGetCarts : 34 | IQueryHandler> 35 | { 36 | private readonly IElasticClient elasticClient; 37 | 38 | public HandleGetCarts(IElasticClient elasticClient) 39 | { 40 | this.elasticClient = elasticClient; 41 | } 42 | 43 | public async Task> Handle(GetCarts request, 44 | CancellationToken cancellationToken) 45 | { 46 | var result = await elasticClient.SearchAsync( 47 | s => s 48 | .Skip(request.PageNumber * request.PageSize) 49 | .Take(request.PageSize), 50 | cancellationToken); 51 | 52 | return result.Documents.ToList(); 53 | } 54 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts/Carts/InitializingCart/CartInitialized.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Carts.Carts.InitializingCart; 4 | 5 | public class CartInitialized 6 | { 7 | public Guid CartId { get; } 8 | 9 | public Guid ClientId { get; } 10 | 11 | public CartStatus CartStatus { get; } 12 | 13 | public CartInitialized(Guid cartId, Guid clientId, CartStatus cartStatus) 14 | { 15 | CartId = cartId; 16 | ClientId = clientId; 17 | CartStatus = cartStatus; 18 | } 19 | 20 | public static CartInitialized Create(Guid cartId, Guid clientId, CartStatus cartStatus) 21 | { 22 | if (cartId == Guid.Empty) 23 | throw new ArgumentOutOfRangeException(nameof(cartId)); 24 | if (clientId == Guid.Empty) 25 | throw new ArgumentOutOfRangeException(nameof(clientId)); 26 | if (cartStatus == default) 27 | throw new ArgumentOutOfRangeException(nameof(cartStatus)); 28 | 29 | return new CartInitialized(cartId, clientId, cartStatus); 30 | } 31 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts/Carts/InitializingCart/InitializeCart.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Core.Commands; 5 | using Core.Repositories; 6 | 7 | namespace Carts.Carts.InitializingCart; 8 | 9 | public class InitializeCart 10 | { 11 | public Guid CartId { get; } 12 | 13 | public Guid ClientId { get; } 14 | 15 | private InitializeCart(Guid cartId, Guid clientId) 16 | { 17 | CartId = cartId; 18 | ClientId = clientId; 19 | } 20 | 21 | public static InitializeCart Create(Guid? cartId, Guid? clientId) 22 | { 23 | if (!cartId.HasValue|| cartId == Guid.Empty) 24 | throw new ArgumentOutOfRangeException(nameof(cartId)); 25 | if (!clientId.HasValue || clientId == Guid.Empty) 26 | throw new ArgumentOutOfRangeException(nameof(clientId)); 27 | 28 | return new InitializeCart(cartId.Value, clientId.Value); 29 | } 30 | } 31 | 32 | internal class HandleInitializeCart: 33 | ICommandHandler 34 | { 35 | private readonly IRepository cartRepository; 36 | 37 | public HandleInitializeCart( 38 | IRepository cartRepository 39 | ) 40 | { 41 | this.cartRepository = cartRepository; 42 | } 43 | 44 | public async Task Handle(InitializeCart command, CancellationToken cancellationToken) 45 | { 46 | var cart = Cart.Initialize(command.CartId, command.ClientId); 47 | 48 | await cartRepository.Add(cart, cancellationToken); 49 | } 50 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts/Carts/RemovingProduct/ProductRemoved.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Carts.Carts.Products; 3 | 4 | namespace Carts.Carts.RemovingProduct; 5 | 6 | public class ProductRemoved 7 | { 8 | public Guid CartId { get; } 9 | 10 | public PricedProductItem ProductItem { get; } 11 | 12 | public ProductRemoved(Guid cartId, PricedProductItem productItem) 13 | { 14 | CartId = cartId; 15 | ProductItem = productItem; 16 | } 17 | 18 | public static ProductRemoved Create(Guid cartId, PricedProductItem productItem) 19 | { 20 | if (cartId == Guid.Empty) 21 | throw new ArgumentOutOfRangeException(nameof(cartId)); 22 | 23 | return new ProductRemoved(cartId, productItem); 24 | } 25 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts/Carts/RemovingProduct/RemoveProduct.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Carts.Carts.Products; 5 | using Core.Commands; 6 | using Core.Repositories; 7 | 8 | namespace Carts.Carts.RemovingProduct; 9 | 10 | public class RemoveProduct 11 | { 12 | public Guid CartId { get; } 13 | 14 | public PricedProductItem ProductItem { get; } 15 | 16 | private RemoveProduct(Guid cardId, PricedProductItem productItem) 17 | { 18 | CartId = cardId; 19 | ProductItem = productItem; 20 | } 21 | 22 | public static RemoveProduct Create(Guid cardId, PricedProductItem productItem) 23 | { 24 | return new(cardId, productItem); 25 | } 26 | } 27 | 28 | internal class HandleRemoveProduct: 29 | ICommandHandler 30 | { 31 | private readonly IRepository cartRepository; 32 | 33 | public HandleRemoveProduct( 34 | IRepository cartRepository 35 | ) 36 | { 37 | this.cartRepository = cartRepository; 38 | } 39 | 40 | public Task Handle(RemoveProduct command, CancellationToken cancellationToken) 41 | { 42 | return cartRepository.GetAndUpdate( 43 | command.CartId, 44 | cart => cart.RemoveProduct(command.ProductItem), 45 | cancellationToken); 46 | } 47 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts/Config.cs: -------------------------------------------------------------------------------- 1 | using Carts.Carts; 2 | using Carts.Carts.GettingCartById; 3 | using Core.ElasticSearch; 4 | using Core.EventStoreDB; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | namespace Carts; 9 | 10 | public static class Config 11 | { 12 | public static void AddCartsModule(this IServiceCollection services, IConfiguration config) 13 | { 14 | services.AddEventStoreDB(config); 15 | // Document Part used for projections 16 | services.AddElasticsearch(config, 17 | settings => settings.DefaultMappingFor(m => m.Ignore(cd => cd.TotalPrice))); 18 | services.AddCarts(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts/Pricing/IProductPriceCalculator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Carts.Carts.Products; 3 | 4 | namespace Carts.Pricing; 5 | 6 | public interface IProductPriceCalculator 7 | { 8 | IReadOnlyList Calculate(params ProductItem[] productItems); 9 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Carts/Carts/Pricing/RandomProductPriceCalculator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Carts.Carts.Products; 5 | 6 | namespace Carts.Pricing; 7 | 8 | public class RandomProductPriceCalculator: IProductPriceCalculator 9 | { 10 | public IReadOnlyList Calculate(params ProductItem[] productItems) 11 | { 12 | if (productItems.Length == 0) 13 | throw new ArgumentOutOfRangeException(nameof(productItems), "Product items cannot be an empty"); 14 | 15 | var random = new Random(); 16 | 17 | return productItems 18 | .Select(pi => 19 | PricedProductItem.Create(pi, (decimal)random.NextDouble() * 100)) 20 | .ToList(); 21 | } 22 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.ElasticSearch/Config.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Nest; 5 | 6 | namespace Core.ElasticSearch; 7 | 8 | public class ElasticSearchConfig 9 | { 10 | public string Url { get; set; } = default!; 11 | public string DefaultIndex { get; set; } = default!; 12 | } 13 | 14 | public static class ElasticSearchConfigExtensions 15 | { 16 | private const string DefaultConfigKey = "ElasticSearch"; 17 | public static void AddElasticsearch( 18 | this IServiceCollection services, IConfiguration configuration, Action? config = null) 19 | { 20 | var elasticSearchConfig = configuration.GetSection(DefaultConfigKey).Get(); 21 | 22 | var settings = new ConnectionSettings(new Uri(elasticSearchConfig.Url)) 23 | .DefaultIndex(elasticSearchConfig.DefaultIndex); 24 | 25 | config?.Invoke(settings); 26 | 27 | var client = new ElasticClient(settings); 28 | 29 | services.AddSingleton(client); 30 | } 31 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.ElasticSearch/Core.ElasticSearch.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.ElasticSearch/Indices/IndexNameMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Linq; 4 | 5 | namespace Core.ElasticSearch.Indices; 6 | 7 | public class IndexNameMapper 8 | { 9 | private static readonly IndexNameMapper Instance = new(); 10 | 11 | private readonly ConcurrentDictionary typeNameMap = new(); 12 | 13 | public static void AddCustomMap(string mappedStreamName) => 14 | AddCustomMap(typeof(TStream), mappedStreamName); 15 | 16 | public static void AddCustomMap(Type streamType, string mappedStreamName) 17 | { 18 | Instance.typeNameMap.AddOrUpdate(streamType, mappedStreamName, (_, _) => mappedStreamName); 19 | } 20 | 21 | public static string ToIndexPrefix() => ToIndexPrefix(typeof(TStream)); 22 | 23 | public static string ToIndexPrefix(Type streamType) => Instance.typeNameMap.GetOrAdd(streamType, (_) => 24 | { 25 | var modulePrefix = streamType.Namespace!.Split(".").First(); 26 | return $"{modulePrefix}-{streamType.Name}".ToLower(); 27 | }); 28 | 29 | public static string ToIndexName(object? tenantId = null) => 30 | ToIndexName(typeof(TStream)); 31 | 32 | public static string ToIndexName(Type streamType, object? tenantId = null) 33 | { 34 | var tenantPrefix = tenantId != null ? $"{tenantId}-" : ""; 35 | 36 | return $"{tenantPrefix}{ToIndexPrefix(streamType)}".ToLower(); 37 | } 38 | 39 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.ElasticSearch/Repository/ElasticSearchRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Core.ElasticSearch.Indices; 5 | using Core.Events; 6 | using Nest; 7 | using IAggregate = Core.Aggregates.IAggregate; 8 | 9 | namespace Core.ElasticSearch.Repository; 10 | 11 | public class ElasticSearchRepository: Repositories.IRepository where T : class, IAggregate, new() 12 | { 13 | private readonly IElasticClient elasticClient; 14 | private readonly IEventBus eventBus; 15 | 16 | public ElasticSearchRepository( 17 | IElasticClient elasticClient, 18 | IEventBus eventBus 19 | ) 20 | { 21 | this.elasticClient = elasticClient ?? throw new ArgumentNullException(nameof(elasticClient)); 22 | this.eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus)); 23 | } 24 | 25 | public async Task Find(Guid id, CancellationToken cancellationToken) 26 | { 27 | var response = await elasticClient.GetAsync(id, ct: cancellationToken); 28 | return response?.Source; 29 | } 30 | 31 | public Task Add(T aggregate, CancellationToken cancellationToken) 32 | { 33 | return elasticClient.IndexAsync(aggregate, i => i.Id(aggregate.Id).Index(IndexNameMapper.ToIndexName()), cancellationToken); 34 | } 35 | 36 | public Task Update(T aggregate, CancellationToken cancellationToken) 37 | { 38 | return elasticClient.UpdateAsync(aggregate.Id, i => i.Doc(aggregate).Index(IndexNameMapper.ToIndexName()), cancellationToken); 39 | } 40 | 41 | public Task Delete(T aggregate, CancellationToken cancellationToken) 42 | { 43 | return elasticClient.DeleteAsync(aggregate.Id, ct: cancellationToken); 44 | } 45 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.EventStoreDB/Core.EventStoreDB.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.EventStoreDB/Events/AggregateStreamExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Core.Events; 5 | using Core.EventStoreDB.Serialization; 6 | using Core.Exceptions; 7 | using Core.Projections; 8 | using EventStore.Client; 9 | 10 | namespace Core.EventStoreDB.Events; 11 | 12 | public static class AggregateStreamExtensions 13 | { 14 | public static async Task AggregateStream( 15 | this EventStoreClient eventStore, 16 | Guid id, 17 | CancellationToken cancellationToken, 18 | ulong? fromVersion = null 19 | ) where T : class, IProjection 20 | { 21 | var readResult = eventStore.ReadStreamAsync( 22 | Direction.Forwards, 23 | StreamNameMapper.ToStreamId(id), 24 | fromVersion ?? StreamPosition.Start, 25 | cancellationToken: cancellationToken 26 | ); 27 | 28 | var readState = await readResult.ReadState; 29 | 30 | if(readState == ReadState.StreamNotFound) 31 | throw AggregateNotFoundException.For(id); 32 | 33 | var aggregate = (T)Activator.CreateInstance(typeof(T), true)!; 34 | 35 | await foreach (var @event in readResult) 36 | { 37 | var eventData = @event.Deserialize(); 38 | 39 | aggregate.When(eventData!); 40 | } 41 | 42 | return aggregate; 43 | } 44 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.EventStoreDB/Events/StreamEventExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Core.Events; 3 | using Core.EventStoreDB.Serialization; 4 | using EventStore.Client; 5 | 6 | namespace Core.EventStoreDB.Events; 7 | 8 | public static class StreamEventExtensions 9 | { 10 | public static StreamEvent? ToStreamEvent(this ResolvedEvent resolvedEvent) 11 | { 12 | var eventData = resolvedEvent.Deserialize(); 13 | if (eventData == null) 14 | return null; 15 | 16 | var metaData = new EventMetadata(resolvedEvent.Event.EventNumber.ToUInt64()); 17 | var type = typeof(StreamEvent<>).MakeGenericType(eventData.GetType()); 18 | return (StreamEvent)Activator.CreateInstance(type, eventData, metaData)!; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.EventStoreDB/Serialization/EventStoreDBSerializer.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using Core.Events; 3 | using EventStore.Client; 4 | using Newtonsoft.Json; 5 | 6 | namespace Core.EventStoreDB.Serialization; 7 | 8 | public static class EventStoreDBSerializer 9 | { 10 | public static T? Deserialize(this ResolvedEvent resolvedEvent) where T : class => 11 | Deserialize(resolvedEvent) as T; 12 | 13 | public static object? Deserialize(this ResolvedEvent resolvedEvent) 14 | { 15 | // get type 16 | var eventType = EventTypeMapper.ToType(resolvedEvent.Event.EventType); 17 | 18 | return eventType != null 19 | // deserialize event 20 | ? JsonConvert.DeserializeObject(Encoding.UTF8.GetString(resolvedEvent.Event.Data.Span), eventType) 21 | : null; 22 | } 23 | 24 | public static EventData ToJsonEventData(this object @event) => 25 | new( 26 | Uuid.NewUuid(), 27 | EventTypeMapper.ToName(@event.GetType()), 28 | Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(@event)), 29 | Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(new { })) 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.EventStoreDB/Subscriptions/ISubscriptionCheckpointRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Core.EventStoreDB.Subscriptions; 5 | 6 | public interface ISubscriptionCheckpointRepository 7 | { 8 | ValueTask Load(string subscriptionId, CancellationToken ct); 9 | 10 | ValueTask Store(string subscriptionId, ulong position, CancellationToken ct); 11 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.EventStoreDB/Subscriptions/InMemorySubscriptionCheckpointRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | 5 | namespace Core.EventStoreDB.Subscriptions; 6 | 7 | public class InMemorySubscriptionCheckpointRepository: ISubscriptionCheckpointRepository 8 | { 9 | private readonly ConcurrentDictionary checkpoints = new(); 10 | 11 | public ValueTask Load(string subscriptionId, CancellationToken ct) 12 | { 13 | return new(checkpoints.TryGetValue(subscriptionId, out var checkpoint) ? checkpoint : null); 14 | } 15 | 16 | public ValueTask Store(string subscriptionId, ulong position, CancellationToken ct) 17 | { 18 | checkpoints.AddOrUpdate(subscriptionId, position,(_, _) => position); 19 | 20 | return ValueTask.CompletedTask; 21 | } 22 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.Testing/AggregateExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using Core.Aggregates; 3 | 4 | namespace Core.Testing; 5 | 6 | public static class AggregateExtensions 7 | { 8 | public static T? PublishedEvent(this IAggregate aggregate) where T : class 9 | { 10 | return aggregate.DequeueUncommittedEvents().LastOrDefault() as T; 11 | } 12 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.Testing/Core.Testing.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | true 7 | false 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.Testing/FakeIdGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Core.Ids; 3 | 4 | namespace Core.Testing; 5 | 6 | public class FakeIdGenerator : IIdGenerator 7 | { 8 | public Guid? LastGeneratedId { get; private set; } 9 | public Guid New() => (LastGeneratedId = Guid.NewGuid()).Value; 10 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.Testing/FakeRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using Core.Aggregates; 7 | using Core.Repositories; 8 | 9 | namespace Core.Testing; 10 | 11 | public class FakeRepository : IRepository where T : IAggregate 12 | { 13 | public Dictionary Aggregates { get; private set; } 14 | 15 | public FakeRepository(params T[] aggregates) 16 | { 17 | Aggregates = aggregates.ToDictionary(ks=> ks.Id, vs => vs); 18 | } 19 | 20 | public Task Find(Guid id, CancellationToken cancellationToken) 21 | { 22 | return Task.FromResult(Aggregates.GetValueOrDefault(id)); 23 | } 24 | 25 | public Task Add(T aggregate, CancellationToken cancellationToken) 26 | { 27 | Aggregates.Add(aggregate.Id, aggregate); 28 | return Task.CompletedTask; 29 | } 30 | 31 | public Task Update(T aggregate, CancellationToken cancellationToken) 32 | { 33 | Aggregates[aggregate.Id] = aggregate; 34 | return Task.CompletedTask; 35 | } 36 | 37 | public Task Delete(T aggregate, CancellationToken cancellationToken) 38 | { 39 | Aggregates.Remove(aggregate.Id); 40 | return Task.CompletedTask; 41 | } 42 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.Testing/ResponseExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Threading.Tasks; 3 | using FluentAssertions; 4 | 5 | namespace Core.Testing; 6 | 7 | public static class ResponseExtensions 8 | { 9 | public static async Task GetResultFromJson(this HttpResponseMessage response) 10 | { 11 | var result = await response.Content.ReadAsStringAsync(); 12 | 13 | result.Should().NotBeNull(); 14 | result.Should().NotBe(string.Empty); 15 | 16 | return result.FromJson(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.Testing/SerializationExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net.Http; 2 | using System.Text; 3 | using Newtonsoft.Json; 4 | 5 | namespace Core.Testing; 6 | 7 | public static class SerializationExtensions 8 | { 9 | /// 10 | /// Deserialize object from json with JsonNet 11 | /// 12 | /// Type of the deserialized object 13 | /// json string 14 | /// deserialized object 15 | public static T FromJson(this string json) 16 | { 17 | return JsonConvert.DeserializeObject(json)!; 18 | } 19 | 20 | /// 21 | /// Serialize object to json with JsonNet 22 | /// 23 | /// object to serialize 24 | /// json string 25 | public static string ToJson(this object obj) 26 | { 27 | return JsonConvert.SerializeObject(obj); 28 | } 29 | 30 | /// 31 | /// Serialize object to json with JsonNet 32 | /// 33 | /// object to serialize 34 | /// json string 35 | public static StringContent ToJsonStringContent(this object obj) 36 | { 37 | return new(obj.ToJson(), Encoding.UTF8, "application/json"); 38 | } 39 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.Testing/TestWebHostBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | namespace Core.Testing; 9 | 10 | public static class TestWebHostBuilder 11 | { 12 | public static IWebHostBuilder Create(Dictionary configuration, Action? configureServices = null) 13 | { 14 | var projectDir = Directory.GetCurrentDirectory(); 15 | configureServices ??= _ => { }; 16 | 17 | return new WebHostBuilder() 18 | .UseEnvironment("Development") 19 | .UseContentRoot(projectDir) 20 | .UseConfiguration(new ConfigurationBuilder() 21 | .SetBasePath(projectDir) 22 | .AddJsonFile("appsettings.json", true) 23 | .AddInMemoryCollection(configuration) 24 | .Build() 25 | ) 26 | .ConfigureServices(configureServices); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.WebApi/Core.WebApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.WebApi/Middlewares/ExceptionHandling/ExceptionHandlingMiddleware.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.Extensions.Logging; 5 | using Newtonsoft.Json; 6 | 7 | namespace Core.WebApi.Middlewares.ExceptionHandling; 8 | 9 | public class ExceptionHandlingMiddleware 10 | { 11 | private readonly RequestDelegate next; 12 | 13 | private readonly ILogger logger; 14 | 15 | public ExceptionHandlingMiddleware(RequestDelegate next, 16 | ILoggerFactory loggerFactory) 17 | { 18 | this.next = next; 19 | logger = loggerFactory.CreateLogger(); 20 | } 21 | 22 | public async Task Invoke(HttpContext context /* other scoped dependencies */) 23 | { 24 | try 25 | { 26 | await next(context); 27 | } 28 | catch (Exception ex) 29 | { 30 | await HandleExceptionAsync(context, ex); 31 | } 32 | } 33 | 34 | private Task HandleExceptionAsync(HttpContext context, Exception exception) 35 | { 36 | logger.LogError(exception, exception.Message); 37 | 38 | var codeInfo = ExceptionToHttpStatusMapper.Map(exception); 39 | 40 | var result = JsonConvert.SerializeObject(new HttpExceptionWrapper((int)codeInfo.Code, codeInfo.Message)); 41 | context.Response.ContentType = "application/json"; 42 | context.Response.StatusCode = (int)codeInfo.Code; 43 | return context.Response.WriteAsync(result); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.WebApi/Middlewares/ExceptionHandling/ExceptionToHttpStatusMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.ComponentModel.DataAnnotations; 3 | using System.Net; 4 | using Core.Exceptions; 5 | 6 | namespace Core.WebApi.Middlewares.ExceptionHandling; 7 | 8 | public class HttpStatusCodeInfo 9 | { 10 | public HttpStatusCode Code { get; } 11 | public string Message { get; } 12 | 13 | public HttpStatusCodeInfo(HttpStatusCode code, string message) 14 | { 15 | Code = code; 16 | Message = message; 17 | } 18 | 19 | public static HttpStatusCodeInfo Create(HttpStatusCode code, string message) 20 | { 21 | return new HttpStatusCodeInfo(code, message); 22 | } 23 | } 24 | 25 | public static class ExceptionToHttpStatusMapper 26 | { 27 | public static HttpStatusCodeInfo Map(Exception exception) 28 | { 29 | var code = exception switch 30 | { 31 | UnauthorizedAccessException _ => HttpStatusCode.Unauthorized, 32 | NotImplementedException _ => HttpStatusCode.NotImplemented, 33 | InvalidOperationException _ => HttpStatusCode.Conflict, 34 | ArgumentException _ => HttpStatusCode.BadRequest, 35 | ValidationException _ => HttpStatusCode.BadRequest, 36 | AggregateNotFoundException _ => HttpStatusCode.NotFound, 37 | _ => HttpStatusCode.InternalServerError 38 | }; 39 | 40 | return new HttpStatusCodeInfo(code, exception.Message); 41 | } 42 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core.WebApi/Middlewares/ExceptionHandling/HttpExceptionWrapper.cs: -------------------------------------------------------------------------------- 1 | namespace Core.WebApi.Middlewares.ExceptionHandling; 2 | 3 | public class HttpExceptionWrapper 4 | { 5 | public int StatusCode { get; } 6 | 7 | public string Error { get; } 8 | 9 | public HttpExceptionWrapper(int statusCode, string error) 10 | { 11 | StatusCode = statusCode; 12 | Error = error; 13 | } 14 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Aggregates/Aggregate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Core.Aggregates; 5 | 6 | public abstract class Aggregate: Aggregate, IAggregate 7 | { 8 | } 9 | 10 | public abstract class Aggregate: IAggregate where T : notnull 11 | { 12 | public T Id { get; protected set; } = default!; 13 | 14 | public int Version { get; protected set; } 15 | 16 | [NonSerialized] private readonly Queue uncommittedEvents = new Queue(); 17 | 18 | public virtual void When(object @event) { } 19 | 20 | public object[] DequeueUncommittedEvents() 21 | { 22 | var dequeuedEvents = uncommittedEvents.ToArray(); 23 | 24 | uncommittedEvents.Clear(); 25 | 26 | return dequeuedEvents; 27 | } 28 | 29 | protected void Enqueue(object @event) 30 | { 31 | uncommittedEvents.Enqueue(@event); 32 | } 33 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Aggregates/IAggregate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Core.Projections; 3 | 4 | namespace Core.Aggregates; 5 | 6 | public interface IAggregate: IAggregate 7 | { 8 | } 9 | 10 | public interface IAggregate: IProjection 11 | { 12 | T Id { get; } 13 | int Version { get; } 14 | 15 | object[] DequeueUncommittedEvents(); 16 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/BackgroundWorkers/BackgroundWorker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.Hosting; 5 | using Microsoft.Extensions.Logging; 6 | 7 | namespace Core.BackgroundWorkers; 8 | 9 | public class BackgroundWorker: IHostedService, IDisposable 10 | { 11 | private Task? executingTask; 12 | private CancellationTokenSource? cts; 13 | private readonly ILogger logger; 14 | private readonly Func perform; 15 | 16 | public BackgroundWorker( 17 | ILogger logger, 18 | Func perform 19 | ) 20 | { 21 | this.logger = logger; 22 | this.perform = perform; 23 | } 24 | 25 | public Task StartAsync(CancellationToken cancellationToken) 26 | { 27 | // Create a linked token so we can trigger cancellation outside of this token's cancellation 28 | cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); 29 | 30 | executingTask = Task.Run(() => perform(cts.Token), cancellationToken); 31 | 32 | return executingTask; 33 | } 34 | 35 | public async Task StopAsync(CancellationToken cancellationToken) 36 | { 37 | // Stop called without start 38 | if (executingTask == null) 39 | return; 40 | 41 | // Signal cancellation to the executing method 42 | cts?.Cancel(); 43 | 44 | // Wait until the issue completes or the stop token triggers 45 | await Task.WhenAny(executingTask, Task.Delay(-1, cancellationToken)); 46 | 47 | // Throw if cancellation triggered 48 | cancellationToken.ThrowIfCancellationRequested(); 49 | 50 | logger.LogInformation("Background worker stopped"); 51 | } 52 | 53 | public void Dispose() 54 | { 55 | cts?.Dispose(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Commands/CommandBus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace Core.Commands; 7 | 8 | public class CommandBus: ICommandBus 9 | { 10 | private readonly IServiceProvider serviceProvider; 11 | 12 | public CommandBus(IServiceProvider serviceProvider) 13 | { 14 | this.serviceProvider = serviceProvider; 15 | } 16 | 17 | public Task Send(TCommand command, CancellationToken ct) 18 | { 19 | var commandHandler = serviceProvider.GetRequiredService>(); 20 | return commandHandler.Handle(command, ct); 21 | } 22 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Commands/Config.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace Core.Commands; 4 | 5 | public static class Config 6 | { 7 | public static IServiceCollection AddCommandHandler( 8 | this IServiceCollection services 9 | ) 10 | where TCommandHandler : class, ICommandHandler 11 | { 12 | return services.AddTransient() 13 | .AddTransient>(sp => sp.GetRequiredService()); 14 | } 15 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Commands/ICommandBus.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Core.Commands; 5 | 6 | public interface ICommandBus 7 | { 8 | Task Send(TCommand command, CancellationToken ct); 9 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Commands/ICommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Core.Commands; 5 | 6 | public interface ICommandHandler 7 | { 8 | Task Handle(TCommand command, CancellationToken ct); 9 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Config.cs: -------------------------------------------------------------------------------- 1 | using Core.Commands; 2 | using Core.Events; 3 | using Core.Ids; 4 | using Core.Queries; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Microsoft.Extensions.DependencyInjection.Extensions; 7 | 8 | namespace Core; 9 | 10 | public static class Config 11 | { 12 | public static IServiceCollection AddCoreServices(this IServiceCollection services) 13 | { 14 | services 15 | .AddScoped() 16 | .AddScoped(); 17 | 18 | services.TryAddSingleton(); 19 | services.TryAddScoped(); 20 | 21 | return services; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | enable 6 | true 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Events/Config.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace Core.Events; 4 | 5 | public static class Config 6 | { 7 | public static IServiceCollection AddEventHandler( 8 | this IServiceCollection services 9 | ) 10 | where TEventHandler : class, IEventHandler 11 | { 12 | return services.AddTransient() 13 | .AddTransient>(sp => sp.GetRequiredService()); 14 | } 15 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Events/EventBus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using Microsoft.Extensions.DependencyInjection; 8 | 9 | namespace Core.Events; 10 | 11 | public class EventBus: IEventBus 12 | { 13 | private readonly IServiceProvider serviceProvider; 14 | private static readonly ConcurrentDictionary PublishMethods = new(); 15 | 16 | public EventBus( 17 | IServiceProvider serviceProvider 18 | ) 19 | { 20 | this.serviceProvider = serviceProvider; 21 | } 22 | 23 | private async Task Publish(TEvent @event, CancellationToken ct) 24 | { 25 | // You can consider adding here a retry policy for event handling 26 | using var scope = serviceProvider.CreateScope(); 27 | 28 | var eventHandlers = 29 | scope.ServiceProvider.GetServices>(); 30 | 31 | foreach (var eventHandler in eventHandlers) 32 | { 33 | await eventHandler.Handle(@event, ct); 34 | } 35 | } 36 | 37 | public Task Publish(object @event, CancellationToken ct) 38 | { 39 | return (Task)GetGenericPublishFor(@event) 40 | .Invoke(this, new[] { @event, ct })!; 41 | } 42 | 43 | private static MethodInfo GetGenericPublishFor(object @event) 44 | { 45 | return PublishMethods.GetOrAdd(@event.GetType(), eventType => 46 | typeof(EventBus) 47 | .GetMethods(BindingFlags.Instance | BindingFlags.NonPublic) 48 | .Single(m => m.Name == nameof(Publish) && m.GetGenericArguments().Any()) 49 | .MakeGenericMethod(eventType) 50 | ); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Events/EventTypeMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using Core.Reflection; 4 | 5 | namespace Core.Events; 6 | 7 | public class EventTypeMapper 8 | { 9 | private static readonly EventTypeMapper Instance = new(); 10 | 11 | private readonly ConcurrentDictionary typeNameMap = new(); 12 | private readonly ConcurrentDictionary typeMap = new(); 13 | 14 | public static void AddCustomMap(string mappedEventTypeName) => AddCustomMap(typeof(T), mappedEventTypeName); 15 | 16 | public static void AddCustomMap(Type eventType, string mappedEventTypeName) 17 | { 18 | Instance.typeNameMap.AddOrUpdate(eventType, mappedEventTypeName, (_, _) => mappedEventTypeName); 19 | Instance.typeMap.AddOrUpdate(mappedEventTypeName, eventType, (_, _) => eventType); 20 | } 21 | 22 | public static string ToName() => ToName(typeof(TEventType)); 23 | 24 | public static string ToName(Type eventType) => Instance.typeNameMap.GetOrAdd(eventType, (_) => 25 | { 26 | var eventTypeName = eventType.FullName!.Replace(".", "_"); 27 | 28 | Instance.typeMap.AddOrUpdate(eventTypeName, eventType, (_, _) => eventType); 29 | 30 | return eventTypeName; 31 | }); 32 | 33 | public static Type? ToType(string eventTypeName) => Instance.typeMap.GetOrAdd(eventTypeName, _ => 34 | { 35 | var type = TypeProvider.GetFirstMatchingTypeFromCurrentDomainAssembly(eventTypeName.Replace("_", ".")); 36 | 37 | if (type == null) 38 | return null; 39 | 40 | Instance.typeNameMap.AddOrUpdate(type, eventTypeName, (_, _) => eventTypeName); 41 | 42 | return type; 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Events/IEventBus.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Core.Events; 5 | 6 | public interface IEventBus 7 | { 8 | Task Publish(object @event, CancellationToken ct); 9 | } 10 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Events/IEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Core.Events; 5 | 6 | public interface IEventHandler 7 | { 8 | Task Handle(TEvent @event, CancellationToken ct); 9 | } 10 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Events/StreamEvent.cs: -------------------------------------------------------------------------------- 1 | namespace Core.Events; 2 | 3 | public record EventMetadata( 4 | ulong StreamRevision 5 | ); 6 | 7 | public class StreamEvent 8 | { 9 | public object Data { get; } 10 | public EventMetadata Metadata { get; } 11 | 12 | public StreamEvent(object data, EventMetadata metadata) 13 | { 14 | Data = data; 15 | Metadata = metadata; 16 | } 17 | } 18 | 19 | public class StreamEvent: StreamEvent where T: notnull 20 | { 21 | public new T Data => (T)base.Data; 22 | 23 | public StreamEvent(T data, EventMetadata metadata) : base(data, metadata) 24 | { 25 | } 26 | } 27 | 28 | -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Events/StreamNameMapper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Linq; 4 | 5 | namespace Core.Events; 6 | 7 | public class StreamNameMapper 8 | { 9 | private static readonly StreamNameMapper Instance = new(); 10 | 11 | private readonly ConcurrentDictionary typeNameMap = new(); 12 | 13 | public static void AddCustomMap(string mappedStreamName) => 14 | AddCustomMap(typeof(TStream), mappedStreamName); 15 | 16 | public static void AddCustomMap(Type streamType, string mappedStreamName) 17 | { 18 | Instance.typeNameMap.AddOrUpdate(streamType, mappedStreamName, (_, _) => mappedStreamName); 19 | } 20 | 21 | public static string ToStreamPrefix() => ToStreamPrefix(typeof(TStream)); 22 | 23 | public static string ToStreamPrefix(Type streamType) => Instance.typeNameMap.GetOrAdd(streamType, (_) => 24 | { 25 | var modulePrefix = streamType.Namespace!.Split(".").First(); 26 | return $"{modulePrefix}_{streamType.Name}"; 27 | }); 28 | 29 | public static string ToStreamId(object aggregateId, object? tenantId = null) => 30 | ToStreamId(typeof(TStream), aggregateId); 31 | 32 | // Generates a stream id in the canonical `{category}-{aggregateId}` format 33 | public static string ToStreamId(Type streamType, object aggregateId, object? tenantId = null) 34 | { 35 | var tenantPrefix = tenantId == null ? $"{tenantId}_" : ""; 36 | var category = ToStreamPrefix(streamType); 37 | 38 | // (Out-of-the box, the category projection treats anything before a `-` separator as the category name) 39 | // For this reason, we place the "{tenantId}_" bit (if present) on the right hand side of the '-' 40 | return $"{category}-{tenantPrefix}{aggregateId}"; 41 | } 42 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Exceptions/AggregateNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Exceptions; 4 | 5 | public class AggregateNotFoundException : Exception 6 | { 7 | public AggregateNotFoundException(string typeName, Guid id): base($"{typeName} with id '{id}' was not found") 8 | { 9 | 10 | } 11 | 12 | public static AggregateNotFoundException For(Guid id) 13 | { 14 | return new AggregateNotFoundException(typeof(T).Name, id); 15 | } 16 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Extensions/ListExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace Core.Extensions; 5 | 6 | public static class ListExtensions 7 | { 8 | public static IList Replace(this IList list, T existingElement, T replacement) 9 | { 10 | var indexOfExistingItem = list.IndexOf(existingElement); 11 | 12 | if (indexOfExistingItem == -1) 13 | throw new ArgumentOutOfRangeException(nameof(existingElement), "Element was not found"); 14 | 15 | list[indexOfExistingItem] = replacement; 16 | 17 | return list; 18 | } 19 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Ids/IIdGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Ids; 4 | 5 | public interface IIdGenerator 6 | { 7 | Guid New(); 8 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Ids/NulloIdGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Core.Ids; 4 | 5 | public class NulloIdGenerator : IIdGenerator 6 | { 7 | public Guid New() => Guid.NewGuid(); 8 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Projections/IProjection.cs: -------------------------------------------------------------------------------- 1 | namespace Core.Projections; 2 | 3 | public interface IProjection 4 | { 5 | void When(object @event); 6 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Queries/Config.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace Core.Queries; 4 | 5 | public static class Config 6 | { 7 | public static IServiceCollection AddQueryHandler( 8 | this IServiceCollection services 9 | ) 10 | where TQueryHandler : class, IQueryHandler 11 | { 12 | return services.AddTransient() 13 | .AddTransient>(sp => sp.GetRequiredService()); 14 | } 15 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Queries/IQueryBus.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Core.Queries; 5 | 6 | public interface IQueryBus 7 | { 8 | Task Send(TQuery query, CancellationToken ct); 9 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Queries/IQueryHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | 4 | namespace Core.Queries; 5 | 6 | public interface IQueryHandler 7 | { 8 | Task Handle(TQuery query, CancellationToken ct); 9 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Queries/QueryBus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace Core.Queries; 7 | 8 | public class QueryBus: IQueryBus 9 | { 10 | private readonly IServiceProvider serviceProvider; 11 | 12 | public QueryBus(IServiceProvider serviceProvider) 13 | { 14 | this.serviceProvider = serviceProvider; 15 | } 16 | 17 | public Task Send(TQuery query, CancellationToken ct) 18 | { 19 | var queryHandler = serviceProvider.GetRequiredService>(); 20 | return queryHandler.Handle(query, ct); 21 | } 22 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Reflection/TypeProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | 4 | namespace Core.Reflection; 5 | 6 | public static class TypeProvider 7 | { 8 | public static Type? GetFirstMatchingTypeFromCurrentDomainAssembly(string typeName) 9 | { 10 | return AppDomain.CurrentDomain.GetAssemblies() 11 | .SelectMany(a => a.GetTypes().Where(x => x.FullName == typeName || x.Name == typeName)) 12 | .FirstOrDefault(); 13 | } 14 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Repositories/IRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Core.Aggregates; 5 | 6 | namespace Core.Repositories; 7 | 8 | public interface IRepository where T : IAggregate 9 | { 10 | Task Find(Guid id, CancellationToken cancellationToken); 11 | 12 | Task Add(T aggregate, CancellationToken cancellationToken); 13 | 14 | Task Update(T aggregate, CancellationToken cancellationToken); 15 | 16 | Task Delete(T aggregate, CancellationToken cancellationToken); 17 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Repositories/RepositoryExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Core.Aggregates; 5 | using Core.Exceptions; 6 | 7 | namespace Core.Repositories; 8 | 9 | public static class RepositoryExtensions 10 | { 11 | public static async Task Get(this IRepository repository, Guid id, CancellationToken cancellationToken = default) where T : IAggregate 12 | { 13 | var entity = await repository.Find(id, cancellationToken); 14 | 15 | return entity ?? throw AggregateNotFoundException.For(id); 16 | } 17 | 18 | public static async Task GetAndUpdate(this IRepository repository, Guid id, Action action, CancellationToken cancellationToken = default) where T : IAggregate 19 | { 20 | var entity = await repository.Get(id, cancellationToken); 21 | 22 | action(entity); 23 | 24 | await repository.Update(entity, cancellationToken); 25 | } 26 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/Core/Core/Threading/NoSynchronizationContextScope.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | 4 | namespace Core.Threading; 5 | 6 | public static class NoSynchronizationContextScope 7 | { 8 | public static Disposable Enter() 9 | { 10 | var context = SynchronizationContext.Current; 11 | SynchronizationContext.SetSynchronizationContext(null); 12 | return new Disposable(context); 13 | } 14 | 15 | public struct Disposable: IDisposable 16 | { 17 | private readonly SynchronizationContext? synchronizationContext; 18 | 19 | public Disposable(SynchronizationContext? synchronizationContext) 20 | { 21 | this.synchronizationContext = synchronizationContext; 22 | } 23 | 24 | public void Dispose() => 25 | SynchronizationContext.SetSynchronizationContext(synchronizationContext); 26 | } 27 | } -------------------------------------------------------------------------------- /CQRS_Flow/Dotnet/ECommerce.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | tab_width = 4 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.java] 14 | max_line_length = 80 15 | 16 | [*.md] 17 | max_line_length = off 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/CQRS_Flow/Java/event-sourcing-esdb-aggregates/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'event-sourcing-esdb-aggregates' 2 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/ECommerceApplication.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public 8 | class ECommerceApplication { 9 | public static void main(String[] args) { 10 | SpringApplication.run(ECommerceApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/api/requests/ShoppingCartsRequests.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.api.requests; 2 | 3 | import org.springframework.validation.annotation.Validated; 4 | 5 | import javax.validation.constraints.NotNull; 6 | import java.util.UUID; 7 | 8 | public final class ShoppingCartsRequests { 9 | public record Open( 10 | @NotNull UUID clientId 11 | ) { 12 | } 13 | 14 | @Validated 15 | public record ProductItemRequest( 16 | @NotNull UUID productId, 17 | @NotNull Integer quantity 18 | ) { 19 | } 20 | 21 | public record AddProduct( 22 | @NotNull ProductItemRequest productItem 23 | ) { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/core/aggregates/AbstractAggregate.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.aggregates; 2 | 3 | import java.util.LinkedList; 4 | import java.util.Queue; 5 | 6 | public abstract class AbstractAggregate implements Aggregate { 7 | protected Id id; 8 | protected int version; 9 | 10 | private final Queue uncommittedEvents = new LinkedList<>(); 11 | 12 | public Id id() { 13 | return id; 14 | } 15 | 16 | public int version() { 17 | return version; 18 | } 19 | 20 | public Object[] dequeueUncommittedEvents() { 21 | var dequeuedEvents = uncommittedEvents.toArray(); 22 | 23 | uncommittedEvents.clear(); 24 | 25 | return dequeuedEvents; 26 | } 27 | 28 | public abstract void when(Event event); 29 | 30 | protected void enqueue(Event event) { 31 | uncommittedEvents.add(event); 32 | when(event); 33 | version++; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/core/aggregates/Aggregate.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.aggregates; 2 | 3 | public interface Aggregate { 4 | Id id(); 5 | int version(); 6 | 7 | Object[] dequeueUncommittedEvents(); 8 | } 9 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/core/config/CoreConfig.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.config; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.eventdriven.ecommerce.core.events.EventBus; 5 | import io.eventdriven.ecommerce.core.events.EventForwarder; 6 | import io.eventdriven.ecommerce.core.serialization.EventSerializer; 7 | import org.springframework.context.ApplicationEventPublisher; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | class CoreConfig { 13 | @Bean 14 | ObjectMapper defaultJSONMapper() { 15 | return EventSerializer.mapper; 16 | } 17 | 18 | @Bean 19 | EventBus eventBus(ApplicationEventPublisher applicationEventPublisher) { 20 | return new EventForwarder(applicationEventPublisher); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/core/events/EventBus.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.events; 2 | 3 | public interface EventBus { 4 | void publish(EventEnvelope event); 5 | } 6 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/core/events/EventEnvelope.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.events; 2 | 3 | import com.eventstore.dbclient.ResolvedEvent; 4 | import io.eventdriven.ecommerce.core.serialization.EventSerializer; 5 | import org.springframework.core.ResolvableType; 6 | import org.springframework.core.ResolvableTypeProvider; 7 | 8 | import java.util.Optional; 9 | 10 | public record EventEnvelope( 11 | Event data, 12 | EventMetadata metadata 13 | ) implements ResolvableTypeProvider { 14 | 15 | public static Optional> of(final Class type, ResolvedEvent resolvedEvent) { 16 | if (type == null) 17 | return Optional.empty(); 18 | 19 | var eventData = EventSerializer.deserialize(type, resolvedEvent); 20 | 21 | if (eventData.isEmpty()) 22 | return Optional.empty(); 23 | 24 | return Optional.of( 25 | new EventEnvelope<>( 26 | eventData.get(), 27 | new EventMetadata( 28 | resolvedEvent.getEvent().getEventId().toString(), 29 | resolvedEvent.getEvent().getStreamRevision().getValueUnsigned(), 30 | resolvedEvent.getEvent().getPosition().getCommitUnsigned(), 31 | resolvedEvent.getEvent().getEventType() 32 | ) 33 | ) 34 | ); 35 | } 36 | 37 | @Override 38 | public ResolvableType getResolvableType() { 39 | return ResolvableType.forClassWithGenerics( 40 | getClass(), ResolvableType.forInstance(data) 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/core/events/EventForwarder.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.events; 2 | 3 | import org.springframework.context.ApplicationEventPublisher; 4 | 5 | public record EventForwarder( 6 | ApplicationEventPublisher applicationEventPublisher 7 | ) implements EventBus { 8 | 9 | @Override 10 | public void publish(EventEnvelope event) { 11 | applicationEventPublisher.publishEvent(event); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/core/events/EventMetadata.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.events; 2 | 3 | public record EventMetadata( 4 | String eventId, 5 | long streamPosition, 6 | long logPosition, 7 | String eventType 8 | ) { 9 | } 10 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/core/events/EventTypeMapper.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.events; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Optional; 6 | 7 | public final class EventTypeMapper { 8 | private static final EventTypeMapper Instance = new EventTypeMapper(); 9 | 10 | private final Map> typeMap = new HashMap<>(); 11 | private final Map typeNameMap = new HashMap<>(); 12 | 13 | public static String toName(Class eventType) { 14 | return Instance.typeNameMap.computeIfAbsent( 15 | eventType, 16 | c -> c.getTypeName().replace(".", "_") 17 | ); 18 | } 19 | 20 | public static Optional toClass(String eventTypeName) { 21 | return Instance.typeMap.computeIfAbsent( 22 | eventTypeName, 23 | c -> { 24 | try { 25 | return Optional.of(Class.forName(eventTypeName.replace("_", "."))); 26 | } catch (ClassNotFoundException e) { 27 | return Optional.empty(); 28 | } 29 | } 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/core/http/ETag.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.http; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | 5 | import java.util.regex.Pattern; 6 | 7 | public record ETag(String value) { 8 | private static final Pattern ETagPattern = Pattern.compile("\"([^\"]*)\""); 9 | 10 | @JsonCreator 11 | public ETag{ 12 | var regexMatcher = ETagPattern.matcher(value); 13 | 14 | if(!regexMatcher.find()) 15 | throw new IllegalArgumentException("Not an ETag header"); 16 | } 17 | 18 | public static ETag weak(Object value){ 19 | return new ETag("W/\"%s\"".formatted(value.toString())); 20 | } 21 | 22 | public Long toLong() { 23 | var regexMatcher = ETagPattern.matcher(value); 24 | regexMatcher.find(); 25 | 26 | return Long.parseLong(regexMatcher.group(1)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/core/subscriptions/CheckpointStored.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.subscriptions; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | record CheckpointStored( 6 | String subscriptionId, 7 | long position, 8 | LocalDateTime checkpointedAt 9 | ) { 10 | } 11 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/core/subscriptions/EventStoreDBSubscriptionToAllOptions.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.subscriptions; 2 | 3 | import com.eventstore.dbclient.SubscribeToAllOptions; 4 | import com.eventstore.dbclient.SubscriptionFilter; 5 | 6 | record EventStoreDBSubscriptionToAllOptions( 7 | String subscriptionId, 8 | boolean ignoreDeserializationErrors, 9 | SubscribeToAllOptions subscribeToAllOptions 10 | ) { 11 | static EventStoreDBSubscriptionToAllOptions getDefault() { 12 | SubscriptionFilter filterOutSystemEvents = SubscriptionFilter.newBuilder() 13 | .withEventTypeRegularExpression("^[^\\$].*") 14 | .build(); 15 | 16 | SubscribeToAllOptions options = SubscribeToAllOptions.get() 17 | .fromStart() 18 | .filter(filterOutSystemEvents); 19 | 20 | return new EventStoreDBSubscriptionToAllOptions("default", true, options); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/core/subscriptions/SubscriptionCheckpointRepository.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.subscriptions; 2 | 3 | import java.util.Optional; 4 | 5 | public interface SubscriptionCheckpointRepository { 6 | Optional load(String subscriptionId); 7 | 8 | void store(String subscriptionId, long position); 9 | } 10 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/core/views/VersionedView.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.views; 2 | 3 | import io.eventdriven.ecommerce.core.events.EventMetadata; 4 | 5 | public interface VersionedView { 6 | long getLastProcessedPosition(); 7 | 8 | void setMetadata(EventMetadata eventMetadata); 9 | } 10 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/package-info.java: -------------------------------------------------------------------------------- 1 | @NonNullFields 2 | @NonNullApi 3 | package io.eventdriven.ecommerce; 4 | 5 | import org.springframework.lang.NonNullApi; 6 | import org.springframework.lang.NonNullFields; 7 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/pricing/PricingConfig.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.pricing; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.context.annotation.ApplicationScope; 6 | 7 | @Configuration 8 | class PricingConfig { 9 | @Bean 10 | @ApplicationScope 11 | ProductPriceCalculator productPriceCalculator() { 12 | return new RandomProductPriceCalculator(); 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/pricing/ProductPriceCalculator.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.pricing; 2 | 3 | import io.eventdriven.ecommerce.shoppingcarts.productitems.PricedProductItem; 4 | import io.eventdriven.ecommerce.shoppingcarts.productitems.ProductItem; 5 | 6 | public interface ProductPriceCalculator { 7 | PricedProductItem calculate(ProductItem productItem); 8 | } 9 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/pricing/RandomProductPriceCalculator.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.pricing; 2 | 3 | import io.eventdriven.ecommerce.shoppingcarts.productitems.PricedProductItem; 4 | import io.eventdriven.ecommerce.shoppingcarts.productitems.ProductItem; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.Random; 9 | import java.util.UUID; 10 | 11 | public class RandomProductPriceCalculator implements ProductPriceCalculator { 12 | private final Map productPrices = new HashMap<>(); 13 | 14 | public PricedProductItem calculate(ProductItem productItem) { 15 | var random = new Random(); 16 | 17 | var price = random.nextDouble() * 100; 18 | 19 | productPrices.putIfAbsent( 20 | productItem.productId(), 21 | price 22 | ); 23 | 24 | return new PricedProductItem(productItem, price); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/shoppingcarts/ShoppingCartEvent.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts; 2 | 3 | import io.eventdriven.ecommerce.shoppingcarts.productitems.PricedProductItem; 4 | 5 | import java.time.LocalDateTime; 6 | import java.util.UUID; 7 | 8 | public sealed interface ShoppingCartEvent { 9 | 10 | record ShoppingCartOpened( 11 | UUID shoppingCartId, 12 | UUID clientId 13 | ) implements ShoppingCartEvent { 14 | } 15 | 16 | record ProductItemAddedToShoppingCart( 17 | UUID shoppingCartId, 18 | PricedProductItem productItem 19 | ) implements ShoppingCartEvent { 20 | } 21 | 22 | record ProductItemRemovedFromShoppingCart( 23 | UUID shoppingCartId, 24 | PricedProductItem productItem 25 | ) implements ShoppingCartEvent { 26 | } 27 | 28 | record ShoppingCartConfirmed( 29 | UUID shoppingCartId, 30 | LocalDateTime confirmedAt 31 | ) implements ShoppingCartEvent { 32 | } 33 | 34 | record ShoppingCartCanceled( 35 | UUID shoppingCartId, 36 | LocalDateTime canceledAt 37 | ) implements ShoppingCartEvent { 38 | } 39 | } 40 | 41 | 42 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/shoppingcarts/ShoppingCartStatus.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts; 2 | 3 | import java.util.EnumSet; 4 | 5 | public enum ShoppingCartStatus { 6 | Pending, 7 | Confirmed, 8 | Canceled; 9 | 10 | public static final EnumSet Closed = EnumSet.of(Confirmed, Canceled); 11 | 12 | public boolean isClosed() { 13 | return Closed.contains(this); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/shoppingcarts/ShoppingCartsConfig.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts; 2 | 3 | import com.eventstore.dbclient.EventStoreDBClient; 4 | import io.eventdriven.ecommerce.core.aggregates.AggregateStore; 5 | import io.eventdriven.ecommerce.pricing.ProductPriceCalculator; 6 | import io.eventdriven.ecommerce.shoppingcarts.gettingbyid.ShoppingCartDetailsRepository; 7 | import io.eventdriven.ecommerce.shoppingcarts.gettingcarts.ShoppingCartShortInfoRepository; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.web.context.annotation.ApplicationScope; 11 | 12 | import java.util.UUID; 13 | 14 | @Configuration 15 | class ShoppingCartsConfig { 16 | @Bean 17 | ShoppingCartService shoppingCartService( 18 | AggregateStore entityStore, 19 | ShoppingCartDetailsRepository detailsRepository, 20 | ShoppingCartShortInfoRepository shortInfoRepository, 21 | ProductPriceCalculator productPriceCalculator 22 | ) { 23 | return new ShoppingCartService( 24 | entityStore, 25 | detailsRepository, 26 | shortInfoRepository, 27 | productPriceCalculator 28 | ); 29 | } 30 | 31 | @Bean 32 | @ApplicationScope 33 | AggregateStore shoppingCartStore(EventStoreDBClient eventStore) { 34 | return new AggregateStore<>( 35 | eventStore, 36 | ShoppingCart::mapToStreamId, 37 | ShoppingCart::empty 38 | ); 39 | } 40 | } 41 | 42 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/shoppingcarts/gettingbyid/GetShoppingCartById.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.gettingbyid; 2 | 3 | import io.eventdriven.ecommerce.core.http.ETag; 4 | import org.springframework.lang.Nullable; 5 | 6 | import java.util.Optional; 7 | import java.util.UUID; 8 | 9 | public record GetShoppingCartById( 10 | UUID shoppingCartId, 11 | @Nullable ETag eTag 12 | ) { 13 | public static Optional handle( 14 | ShoppingCartDetailsRepository repository, 15 | GetShoppingCartById query 16 | ) { 17 | return query.eTag() == null ? 18 | repository.findById(query.shoppingCartId()) 19 | : repository.findByIdAndNeverVersion(query.shoppingCartId(), query.eTag().toLong()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/shoppingcarts/gettingbyid/ShoppingCartDetailsProductItem.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.gettingbyid; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.Id; 6 | import java.util.UUID; 7 | 8 | @Entity 9 | public class ShoppingCartDetailsProductItem { 10 | @Id 11 | private UUID productId; 12 | 13 | @Column(nullable = false) 14 | private int quantity; 15 | 16 | @Column(nullable = false) 17 | private double unitPrice; 18 | 19 | public ShoppingCartDetailsProductItem(UUID productId, int quantity, double unitPrice) { 20 | this.productId = productId; 21 | this.quantity = quantity; 22 | this.unitPrice = unitPrice; 23 | } 24 | 25 | public ShoppingCartDetailsProductItem() { 26 | 27 | } 28 | 29 | public UUID getProductId() { 30 | return productId; 31 | } 32 | 33 | public void setProductId(UUID productId) { 34 | this.productId = productId; 35 | } 36 | 37 | public int getQuantity() { 38 | return quantity; 39 | } 40 | 41 | public void setQuantity(int quantity) { 42 | this.quantity = quantity; 43 | } 44 | 45 | public void increaseQuantity(int quantity) { 46 | this.quantity += quantity; 47 | } 48 | 49 | public void decreaseQuantity(int quantity) { 50 | this.quantity -= quantity; 51 | } 52 | 53 | public double getUnitPrice() { 54 | return unitPrice; 55 | } 56 | 57 | public void setUnitPrice(double unitPrice) { 58 | this.unitPrice = unitPrice; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/shoppingcarts/gettingbyid/ShoppingCartDetailsRepository.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.gettingbyid; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.data.jpa.repository.Query; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.Optional; 8 | import java.util.UUID; 9 | 10 | @Repository 11 | public interface ShoppingCartDetailsRepository 12 | extends JpaRepository { 13 | @Query("SELECT d FROM ShoppingCartDetails d WHERE d.id = ?1 AND d.version > ?2") 14 | Optional findByIdAndNeverVersion(UUID id, long version); 15 | } 16 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/shoppingcarts/gettingcarts/GetShoppingCarts.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.gettingcarts; 2 | 3 | import org.springframework.data.domain.Page; 4 | import org.springframework.data.domain.PageRequest; 5 | import org.springframework.lang.Nullable; 6 | 7 | public record GetShoppingCarts( 8 | int pageNumber, 9 | int pageSize 10 | ) { 11 | public GetShoppingCarts { 12 | if (pageNumber < 0) 13 | throw new IllegalArgumentException("Page number has to be a zero-based number"); 14 | 15 | if (pageSize < 0) 16 | throw new IllegalArgumentException("Page size has to be a zero-based number"); 17 | } 18 | 19 | public static GetShoppingCarts of(@Nullable Integer pageNumber, @Nullable Integer pageSize) { 20 | 21 | return new GetShoppingCarts( 22 | pageNumber != null ? pageNumber : 0, 23 | pageSize != null ? pageSize : 20 24 | ); 25 | } 26 | 27 | public static Page handle( 28 | ShoppingCartShortInfoRepository repository, 29 | GetShoppingCarts query 30 | ) { 31 | return repository.findAll( 32 | PageRequest.of(query.pageNumber(), query.pageSize()) 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/shoppingcarts/gettingcarts/ShoppingCartShortInfoRepository.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.gettingcarts; 2 | 3 | import org.springframework.data.repository.PagingAndSortingRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.UUID; 7 | 8 | @Repository 9 | public interface ShoppingCartShortInfoRepository 10 | extends PagingAndSortingRepository { 11 | } 12 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/shoppingcarts/productitems/PricedProductItem.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.productitems; 2 | 3 | import java.util.UUID; 4 | 5 | public record PricedProductItem( 6 | ProductItem productItem, 7 | double unitPrice 8 | ) { 9 | public PricedProductItem { 10 | if (unitPrice <= 0) 11 | throw new IllegalArgumentException("Unit Price has to be a positive number"); 12 | } 13 | 14 | public UUID productId() { 15 | return productItem.productId(); 16 | } 17 | 18 | public int quantity() { 19 | return productItem.quantity(); 20 | } 21 | 22 | public double totalPrice() { 23 | return quantity() * unitPrice(); 24 | } 25 | 26 | boolean matchesProductAndUnitPrice(PricedProductItem pricedProductItem) { 27 | return productId().equals(pricedProductItem.productId()) && unitPrice() == pricedProductItem.unitPrice(); 28 | } 29 | 30 | PricedProductItem mergeWith(PricedProductItem productItem) { 31 | if (!productId().equals(productItem.productId())) 32 | throw new IllegalArgumentException("Product ids do not match."); 33 | if (unitPrice() != productItem.unitPrice()) 34 | throw new IllegalArgumentException("Product unit prices do not match."); 35 | 36 | return new PricedProductItem( 37 | new ProductItem(productId(), productItem.quantity() + productItem.quantity()), 38 | unitPrice() 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/shoppingcarts/productitems/ProductItem.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.productitems; 2 | 3 | import java.util.UUID; 4 | 5 | public record ProductItem( 6 | UUID productId, 7 | int quantity 8 | ) { 9 | public ProductItem { 10 | if (quantity <= 0) 11 | throw new IllegalArgumentException("Quantity has to be a positive number"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/java/io/eventdriven/ecommerce/shoppingcarts/productitems/ProductItems.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.productitems; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Optional; 6 | 7 | public record ProductItems( 8 | List items 9 | ) { 10 | public ProductItems add(PricedProductItem productItem) { 11 | var clone = new ArrayList<>(items); 12 | 13 | var currentProductItem = find(productItem); 14 | 15 | if (currentProductItem.isEmpty()) 16 | clone.add(productItem); 17 | else 18 | clone.set(clone.indexOf(currentProductItem.get()), currentProductItem.get().mergeWith(productItem)); 19 | 20 | return new ProductItems(clone); 21 | } 22 | 23 | public ProductItems remove(PricedProductItem productItem) { 24 | var clone = new ArrayList<>(items); 25 | 26 | var currentProductItem = assertThatCanRemove(productItem); 27 | 28 | clone.remove(currentProductItem); 29 | 30 | return new ProductItems(clone); 31 | } 32 | 33 | Optional find(PricedProductItem productItem) { 34 | return items.stream().filter(pi -> pi.matchesProductAndUnitPrice(productItem)).findAny(); 35 | } 36 | 37 | public PricedProductItem assertThatCanRemove(PricedProductItem productItem) { 38 | 39 | var currentProductItem = find(productItem); 40 | 41 | if (currentProductItem.isEmpty()) 42 | throw new IllegalStateException("Product item wasn't found"); 43 | 44 | if(currentProductItem.get().quantity() < productItem.quantity()) 45 | throw new IllegalStateException("Not enough product items"); 46 | 47 | return currentProductItem.get(); 48 | } 49 | 50 | public static ProductItems empty() { 51 | return new ProductItems(new ArrayList<>()); 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return "ProductItemsList{items=%s}".formatted(items); 57 | } 58 | } 59 | 60 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.jpa.database=POSTGRESQL 2 | spring.sql.init.platform= postgres 3 | spring.datasource.url=jdbc:postgresql://localhost:5432/postgres 4 | spring.datasource.username=postgres 5 | spring.datasource.password=Password12! 6 | spring.jpa.show-sql=true 7 | spring.jpa.generate-ddl=true 8 | spring.jpa.hibernate.ddl-auto=update 9 | spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true 10 | esdb.connectionstring=esdb://localhost:2113?tls=false 11 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | %d %p %c{1.} [%t] %m%n 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/resources/schema-postgres.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/main/resources/schema-postgres.sql -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/test/java/io/eventdriven/ecommerce/api/controller/OpenShoppingCartTests.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.api.controller; 2 | 3 | import io.eventdriven.ecommerce.ECommerceApplication; 4 | import io.eventdriven.ecommerce.api.requests.ShoppingCartsRequests; 5 | import io.eventdriven.ecommerce.testing.ApiSpecification; 6 | import org.json.JSONObject; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | 10 | import java.util.UUID; 11 | 12 | import static io.eventdriven.ecommerce.testing.HttpEntityUtils.toHttpEntity; 13 | 14 | @SpringBootTest(classes = ECommerceApplication.class, 15 | webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 16 | public class OpenShoppingCartTests extends ApiSpecification { 17 | public OpenShoppingCartTests() { 18 | super("api/shopping-carts"); 19 | } 20 | 21 | @Test 22 | public void openShoppingCart_succeeds_forValidData() { 23 | given(() -> new ShoppingCartsRequests.Open(UUID.randomUUID())) 24 | .when(POST) 25 | .then(CREATED); 26 | } 27 | 28 | @Test 29 | public void openShoppingCart_fails_withBadRequest_forInvalidBody() { 30 | given(() -> toHttpEntity(new JSONObject())) 31 | .when(POST) 32 | .then(BAD_REQUEST); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-aggregates/src/test/java/io/eventdriven/ecommerce/testing/HttpEntityUtils.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.testing; 2 | 3 | import org.springframework.http.HttpEntity; 4 | import org.springframework.http.HttpHeaders; 5 | import org.springframework.http.MediaType; 6 | 7 | public final class HttpEntityUtils { 8 | public static HttpEntity toHttpEntity(org.json.JSONObject jsonBody){ 9 | var headers = new HttpHeaders(); 10 | headers.setContentType(MediaType.APPLICATION_JSON); 11 | 12 | return new HttpEntity<>(jsonBody.toString(), headers); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | tab_width = 4 10 | insert_final_newline = true 11 | trim_trailing_whitespace = true 12 | 13 | [*.java] 14 | max_line_length = 80 15 | 16 | [*.md] 17 | max_line_length = off 18 | trim_trailing_whitespace = false 19 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/CQRS_Flow/Java/event-sourcing-esdb-simple/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'event-sourcing-esdb-simple' 2 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/ECommerceApplication.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public 8 | class ECommerceApplication { 9 | public static void main(String[] args) { 10 | SpringApplication.run(ECommerceApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/api/requests/ShoppingCartsRequests.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.api.requests; 2 | 3 | import org.springframework.validation.annotation.Validated; 4 | 5 | import javax.validation.constraints.NotNull; 6 | import java.util.UUID; 7 | 8 | public final class ShoppingCartsRequests { 9 | public record Open( 10 | @NotNull UUID clientId 11 | ) { 12 | } 13 | 14 | @Validated 15 | public record ProductItemRequest( 16 | @NotNull UUID productId, 17 | @NotNull Integer quantity 18 | ) { 19 | } 20 | 21 | public record AddProduct( 22 | @NotNull ProductItemRequest productItem 23 | ) { 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/core/config/CoreConfig.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.config; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import io.eventdriven.ecommerce.core.events.EventBus; 5 | import io.eventdriven.ecommerce.core.events.EventForwarder; 6 | import io.eventdriven.ecommerce.core.serialization.EventSerializer; 7 | import org.springframework.context.ApplicationEventPublisher; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | @Configuration 12 | class CoreConfig { 13 | @Bean 14 | ObjectMapper defaultJSONMapper() { 15 | return EventSerializer.mapper; 16 | } 17 | 18 | @Bean 19 | EventBus eventBus(ApplicationEventPublisher applicationEventPublisher) { 20 | return new EventForwarder(applicationEventPublisher); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/core/events/EventBus.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.events; 2 | 3 | public interface EventBus { 4 | void publish(EventEnvelope event); 5 | } 6 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/core/events/EventEnvelope.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.events; 2 | 3 | import com.eventstore.dbclient.ResolvedEvent; 4 | import io.eventdriven.ecommerce.core.serialization.EventSerializer; 5 | import org.springframework.core.ResolvableType; 6 | import org.springframework.core.ResolvableTypeProvider; 7 | 8 | import java.util.Optional; 9 | 10 | public record EventEnvelope( 11 | Event data, 12 | EventMetadata metadata 13 | ) implements ResolvableTypeProvider { 14 | 15 | public static Optional> of(final Class type, ResolvedEvent resolvedEvent) { 16 | if (type == null) 17 | return Optional.empty(); 18 | 19 | var eventData = EventSerializer.deserialize(type, resolvedEvent); 20 | 21 | if (eventData.isEmpty()) 22 | return Optional.empty(); 23 | 24 | return Optional.of( 25 | new EventEnvelope<>( 26 | eventData.get(), 27 | new EventMetadata( 28 | resolvedEvent.getEvent().getEventId().toString(), 29 | resolvedEvent.getEvent().getStreamRevision().getValueUnsigned(), 30 | resolvedEvent.getEvent().getPosition().getCommitUnsigned(), 31 | resolvedEvent.getEvent().getEventType() 32 | ) 33 | ) 34 | ); 35 | } 36 | 37 | @Override 38 | public ResolvableType getResolvableType() { 39 | return ResolvableType.forClassWithGenerics( 40 | getClass(), ResolvableType.forInstance(data) 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/core/events/EventForwarder.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.events; 2 | 3 | import org.springframework.context.ApplicationEventPublisher; 4 | 5 | public record EventForwarder( 6 | ApplicationEventPublisher applicationEventPublisher 7 | ) implements EventBus { 8 | 9 | @Override 10 | public void publish(EventEnvelope event) { 11 | applicationEventPublisher.publishEvent(event); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/core/events/EventMetadata.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.events; 2 | 3 | public record EventMetadata( 4 | String eventId, 5 | long streamPosition, 6 | long logPosition, 7 | String eventType 8 | ) { 9 | } 10 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/core/events/EventTypeMapper.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.events; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.Optional; 6 | 7 | public final class EventTypeMapper { 8 | private static final EventTypeMapper Instance = new EventTypeMapper(); 9 | 10 | private final Map> typeMap = new HashMap<>(); 11 | private final Map typeNameMap = new HashMap<>(); 12 | 13 | public static String toName(Class eventType) { 14 | return Instance.typeNameMap.computeIfAbsent( 15 | eventType, 16 | c -> c.getTypeName().replace(".", "_") 17 | ); 18 | } 19 | 20 | public static Optional toClass(String eventTypeName) { 21 | return Instance.typeMap.computeIfAbsent( 22 | eventTypeName, 23 | c -> { 24 | try { 25 | return Optional.of(Class.forName(eventTypeName.replace("_", "."))); 26 | } catch (ClassNotFoundException e) { 27 | return Optional.empty(); 28 | } 29 | } 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/core/http/ETag.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.http; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | 5 | import java.util.regex.Pattern; 6 | 7 | public record ETag(String value) { 8 | private static final Pattern ETagPattern = Pattern.compile("\"([^\"]*)\""); 9 | 10 | @JsonCreator 11 | public ETag{ 12 | var regexMatcher = ETagPattern.matcher(value); 13 | 14 | if(!regexMatcher.find()) 15 | throw new IllegalArgumentException("Not an ETag header"); 16 | } 17 | 18 | public static ETag weak(Object value){ 19 | return new ETag("W/\"%s\"".formatted(value.toString())); 20 | } 21 | 22 | public Long toLong() { 23 | var regexMatcher = ETagPattern.matcher(value); 24 | regexMatcher.find(); 25 | 26 | return Long.parseLong(regexMatcher.group(1)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/core/subscriptions/CheckpointStored.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.subscriptions; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | record CheckpointStored( 6 | String subscriptionId, 7 | long position, 8 | LocalDateTime checkpointedAt 9 | ) { 10 | } 11 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/core/subscriptions/EventStoreDBSubscriptionToAllOptions.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.subscriptions; 2 | 3 | import com.eventstore.dbclient.SubscribeToAllOptions; 4 | import com.eventstore.dbclient.SubscriptionFilter; 5 | 6 | record EventStoreDBSubscriptionToAllOptions( 7 | String subscriptionId, 8 | boolean ignoreDeserializationErrors, 9 | SubscribeToAllOptions subscribeToAllOptions 10 | ) { 11 | static EventStoreDBSubscriptionToAllOptions getDefault() { 12 | SubscriptionFilter filterOutSystemEvents = SubscriptionFilter.newBuilder() 13 | .withEventTypeRegularExpression("^[^\\$].*") 14 | .build(); 15 | 16 | SubscribeToAllOptions options = SubscribeToAllOptions.get() 17 | .fromStart() 18 | .filter(filterOutSystemEvents); 19 | 20 | return new EventStoreDBSubscriptionToAllOptions("default", true, options); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/core/subscriptions/SubscriptionCheckpointRepository.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.subscriptions; 2 | 3 | import java.util.Optional; 4 | 5 | public interface SubscriptionCheckpointRepository { 6 | Optional load(String subscriptionId); 7 | 8 | void store(String subscriptionId, long position); 9 | } 10 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/core/views/VersionedView.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.core.views; 2 | 3 | import io.eventdriven.ecommerce.core.events.EventMetadata; 4 | 5 | public interface VersionedView { 6 | long getLastProcessedPosition(); 7 | 8 | void setMetadata(EventMetadata eventMetadata); 9 | } 10 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/package-info.java: -------------------------------------------------------------------------------- 1 | @NonNullFields 2 | @NonNullApi 3 | package io.eventdriven.ecommerce; 4 | 5 | import org.springframework.lang.NonNullApi; 6 | import org.springframework.lang.NonNullFields; 7 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/pricing/PricingConfig.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.pricing; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.context.annotation.ApplicationScope; 6 | 7 | @Configuration 8 | class PricingConfig { 9 | @Bean 10 | @ApplicationScope 11 | ProductPriceCalculator productPriceCalculator() { 12 | return new RandomProductPriceCalculator(); 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/pricing/ProductPriceCalculator.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.pricing; 2 | 3 | import io.eventdriven.ecommerce.shoppingcarts.productitems.PricedProductItem; 4 | import io.eventdriven.ecommerce.shoppingcarts.productitems.ProductItem; 5 | 6 | public interface ProductPriceCalculator { 7 | PricedProductItem calculate(ProductItem productItem); 8 | } 9 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/pricing/RandomProductPriceCalculator.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.pricing; 2 | 3 | import io.eventdriven.ecommerce.shoppingcarts.productitems.PricedProductItem; 4 | import io.eventdriven.ecommerce.shoppingcarts.productitems.ProductItem; 5 | 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.Random; 9 | import java.util.UUID; 10 | 11 | public class RandomProductPriceCalculator implements ProductPriceCalculator { 12 | private final Map productPrices = new HashMap<>(); 13 | 14 | public PricedProductItem calculate(ProductItem productItem) { 15 | var random = new Random(); 16 | 17 | var price = random.nextDouble() * 100; 18 | 19 | productPrices.putIfAbsent( 20 | productItem.productId(), 21 | price 22 | ); 23 | 24 | return new PricedProductItem(productItem, price); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/shoppingcarts/ShoppingCartEvent.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts; 2 | 3 | import io.eventdriven.ecommerce.shoppingcarts.productitems.PricedProductItem; 4 | 5 | import java.time.LocalDateTime; 6 | import java.util.UUID; 7 | 8 | public sealed interface ShoppingCartEvent { 9 | 10 | record ShoppingCartOpened( 11 | UUID shoppingCartId, 12 | UUID clientId 13 | ) implements ShoppingCartEvent { 14 | } 15 | 16 | record ProductItemAddedToShoppingCart( 17 | UUID shoppingCartId, 18 | PricedProductItem productItem 19 | ) implements ShoppingCartEvent { 20 | } 21 | 22 | record ProductItemRemovedFromShoppingCart( 23 | UUID shoppingCartId, 24 | PricedProductItem productItem 25 | ) implements ShoppingCartEvent { 26 | } 27 | 28 | record ShoppingCartConfirmed( 29 | UUID shoppingCartId, 30 | LocalDateTime confirmedAt 31 | ) implements ShoppingCartEvent { 32 | } 33 | 34 | record ShoppingCartCanceled( 35 | UUID shoppingCartId, 36 | LocalDateTime canceledAt 37 | ) implements ShoppingCartEvent { 38 | } 39 | } 40 | 41 | 42 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/shoppingcarts/ShoppingCartsConfig.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts; 2 | 3 | import com.eventstore.dbclient.EventStoreDBClient; 4 | import io.eventdriven.ecommerce.core.entities.EntityStore; 5 | import io.eventdriven.ecommerce.pricing.ProductPriceCalculator; 6 | import io.eventdriven.ecommerce.shoppingcarts.gettingbyid.ShoppingCartDetailsRepository; 7 | import io.eventdriven.ecommerce.shoppingcarts.gettingcarts.ShoppingCartShortInfoRepository; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.web.context.annotation.ApplicationScope; 11 | 12 | @Configuration 13 | class ShoppingCartsConfig { 14 | @Bean 15 | ShoppingCartService shoppingCartService( 16 | EntityStore entityStore, 17 | ShoppingCartDetailsRepository detailsRepository, 18 | ShoppingCartShortInfoRepository shortInfoRepository, 19 | ProductPriceCalculator productPriceCalculator 20 | ) { 21 | return new ShoppingCartService( 22 | entityStore, 23 | detailsRepository, 24 | shortInfoRepository, 25 | productPriceCalculator 26 | ); 27 | } 28 | 29 | @Bean 30 | @ApplicationScope 31 | EntityStore shoppingCartStore(EventStoreDBClient eventStore) { 32 | return new EntityStore<>( 33 | eventStore, 34 | ShoppingCart::when, 35 | ShoppingCart::mapToStreamId, 36 | ShoppingCart::empty 37 | ); 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/shoppingcarts/addingproductitem/AddProductItemToShoppingCart.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.addingproductitem; 2 | 3 | import io.eventdriven.ecommerce.pricing.ProductPriceCalculator; 4 | import io.eventdriven.ecommerce.shoppingcarts.ShoppingCart; 5 | import io.eventdriven.ecommerce.shoppingcarts.ShoppingCartEvent.ProductItemAddedToShoppingCart; 6 | import io.eventdriven.ecommerce.shoppingcarts.productitems.ProductItem; 7 | 8 | import java.util.UUID; 9 | 10 | public record AddProductItemToShoppingCart( 11 | UUID shoppingCartId, 12 | ProductItem productItem, 13 | Long expectedVersion 14 | ) { 15 | public static ProductItemAddedToShoppingCart handle( 16 | ProductPriceCalculator productPriceCalculator, 17 | AddProductItemToShoppingCart command, 18 | ShoppingCart shoppingCart 19 | ) { 20 | if (shoppingCart.isClosed()) 21 | throw new IllegalStateException("Removing product item for cart in '%s' status is not allowed.".formatted(shoppingCart.status())); 22 | 23 | var pricedProductItem = productPriceCalculator.calculate(command.productItem); 24 | 25 | shoppingCart.productItems().add(pricedProductItem); 26 | 27 | return new ProductItemAddedToShoppingCart( 28 | command.shoppingCartId, 29 | pricedProductItem 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/shoppingcarts/canceling/CancelShoppingCart.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.canceling; 2 | 3 | import io.eventdriven.ecommerce.shoppingcarts.ShoppingCartEvent.ShoppingCartCanceled; 4 | import io.eventdriven.ecommerce.shoppingcarts.ShoppingCart; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.UUID; 8 | 9 | public record CancelShoppingCart( 10 | UUID shoppingCartId, 11 | Long expectedVersion 12 | ) { 13 | public static ShoppingCartCanceled handle(CancelShoppingCart command, ShoppingCart shoppingCart) { 14 | if (shoppingCart.isClosed()) 15 | throw new IllegalStateException("Canceling cart in '%s' status is not allowed.".formatted(shoppingCart.status())); 16 | 17 | return new ShoppingCartCanceled( 18 | shoppingCart.id(), 19 | LocalDateTime.now() 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/shoppingcarts/confirming/ConfirmShoppingCart.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.confirming; 2 | 3 | import io.eventdriven.ecommerce.shoppingcarts.ShoppingCartEvent.ShoppingCartConfirmed; 4 | import io.eventdriven.ecommerce.shoppingcarts.ShoppingCart; 5 | 6 | import java.time.LocalDateTime; 7 | import java.util.UUID; 8 | 9 | public record ConfirmShoppingCart( 10 | UUID shoppingCartId, 11 | Long expectedVersion 12 | ) { 13 | public static ShoppingCartConfirmed handle(ConfirmShoppingCart command, ShoppingCart shoppingCart) { 14 | if (shoppingCart.isClosed()) 15 | throw new IllegalStateException("Confirming cart in '%s' status is not allowed.".formatted(shoppingCart.status())); 16 | 17 | return new ShoppingCartConfirmed( 18 | shoppingCart.id(), 19 | LocalDateTime.now() 20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/shoppingcarts/gettingbyid/GetShoppingCartById.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.gettingbyid; 2 | 3 | import io.eventdriven.ecommerce.core.http.ETag; 4 | import org.springframework.lang.Nullable; 5 | 6 | import java.util.Optional; 7 | import java.util.UUID; 8 | 9 | public record GetShoppingCartById( 10 | UUID shoppingCartId, 11 | @Nullable ETag eTag 12 | ) { 13 | public static Optional handle( 14 | ShoppingCartDetailsRepository repository, 15 | GetShoppingCartById query 16 | ) { 17 | return query.eTag() == null ? 18 | repository.findById(query.shoppingCartId()) 19 | : repository.findByIdAndNeverVersion(query.shoppingCartId(), query.eTag().toLong()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/shoppingcarts/gettingbyid/ShoppingCartDetailsProductItem.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.gettingbyid; 2 | 3 | import javax.persistence.Column; 4 | import javax.persistence.Entity; 5 | import javax.persistence.Id; 6 | import java.util.UUID; 7 | 8 | @Entity 9 | public class ShoppingCartDetailsProductItem { 10 | @Id 11 | private UUID productId; 12 | 13 | @Column(nullable = false) 14 | private int quantity; 15 | 16 | @Column(nullable = false) 17 | private double unitPrice; 18 | 19 | public ShoppingCartDetailsProductItem(UUID productId, int quantity, double unitPrice) { 20 | this.productId = productId; 21 | this.quantity = quantity; 22 | this.unitPrice = unitPrice; 23 | } 24 | 25 | public ShoppingCartDetailsProductItem() { 26 | 27 | } 28 | 29 | public UUID getProductId() { 30 | return productId; 31 | } 32 | 33 | public void setProductId(UUID productId) { 34 | this.productId = productId; 35 | } 36 | 37 | public int getQuantity() { 38 | return quantity; 39 | } 40 | 41 | public void setQuantity(int quantity) { 42 | this.quantity = quantity; 43 | } 44 | 45 | public void increaseQuantity(int quantity) { 46 | this.quantity += quantity; 47 | } 48 | 49 | public void decreaseQuantity(int quantity) { 50 | this.quantity -= quantity; 51 | } 52 | 53 | public double getUnitPrice() { 54 | return unitPrice; 55 | } 56 | 57 | public void setUnitPrice(double unitPrice) { 58 | this.unitPrice = unitPrice; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/shoppingcarts/gettingbyid/ShoppingCartDetailsRepository.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.gettingbyid; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.data.jpa.repository.Query; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.Optional; 8 | import java.util.UUID; 9 | 10 | @Repository 11 | public interface ShoppingCartDetailsRepository 12 | extends JpaRepository { 13 | @Query("SELECT d FROM ShoppingCartDetails d WHERE d.id = ?1 AND d.version > ?2") 14 | Optional findByIdAndNeverVersion(UUID id, long version); 15 | } 16 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/shoppingcarts/gettingcarts/GetShoppingCarts.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.gettingcarts; 2 | 3 | import org.springframework.data.domain.Page; 4 | import org.springframework.data.domain.PageRequest; 5 | import org.springframework.lang.Nullable; 6 | 7 | public record GetShoppingCarts( 8 | int pageNumber, 9 | int pageSize 10 | ) { 11 | public GetShoppingCarts { 12 | if (pageNumber < 0) 13 | throw new IllegalArgumentException("Page number has to be a zero-based number"); 14 | 15 | if (pageSize < 0) 16 | throw new IllegalArgumentException("Page size has to be a zero-based number"); 17 | } 18 | 19 | public static GetShoppingCarts of(@Nullable Integer pageNumber, @Nullable Integer pageSize) { 20 | 21 | return new GetShoppingCarts( 22 | pageNumber != null ? pageNumber : 0, 23 | pageSize != null ? pageSize : 20 24 | ); 25 | } 26 | 27 | public static Page handle( 28 | ShoppingCartShortInfoRepository repository, 29 | GetShoppingCarts query 30 | ) { 31 | return repository.findAll( 32 | PageRequest.of(query.pageNumber(), query.pageSize()) 33 | ); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/shoppingcarts/gettingcarts/ShoppingCartShortInfoRepository.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.gettingcarts; 2 | 3 | import org.springframework.data.repository.PagingAndSortingRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.UUID; 7 | 8 | @Repository 9 | public interface ShoppingCartShortInfoRepository 10 | extends PagingAndSortingRepository { 11 | } 12 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/shoppingcarts/opening/OpenShoppingCart.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.opening; 2 | 3 | import io.eventdriven.ecommerce.shoppingcarts.ShoppingCartEvent.ShoppingCartOpened; 4 | 5 | import java.util.UUID; 6 | 7 | public record OpenShoppingCart( 8 | UUID shoppingCartId, 9 | UUID clientId 10 | ) { 11 | public static ShoppingCartOpened handle(OpenShoppingCart command) { 12 | return new ShoppingCartOpened( 13 | command.shoppingCartId(), 14 | command.clientId() 15 | ); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/shoppingcarts/productitems/PricedProductItem.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.productitems; 2 | 3 | import java.util.UUID; 4 | 5 | public record PricedProductItem( 6 | ProductItem productItem, 7 | double unitPrice 8 | ) { 9 | public PricedProductItem { 10 | if (unitPrice <= 0) 11 | throw new IllegalArgumentException("Unit Price has to be a positive number"); 12 | } 13 | 14 | public UUID productId() { 15 | return productItem.productId(); 16 | } 17 | 18 | public int quantity() { 19 | return productItem.quantity(); 20 | } 21 | 22 | public double totalPrice() { 23 | return quantity() * unitPrice(); 24 | } 25 | 26 | boolean matchesProductAndUnitPrice(PricedProductItem pricedProductItem) { 27 | return productId().equals(pricedProductItem.productId()) && unitPrice() == pricedProductItem.unitPrice(); 28 | } 29 | 30 | PricedProductItem mergeWith(PricedProductItem productItem) { 31 | if (!productId().equals(productItem.productId())) 32 | throw new IllegalArgumentException("Product ids do not match."); 33 | if (unitPrice() != productItem.unitPrice()) 34 | throw new IllegalArgumentException("Product unit prices do not match."); 35 | 36 | return new PricedProductItem( 37 | new ProductItem(productId(), productItem.quantity() + productItem.quantity()), 38 | unitPrice() 39 | ); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/shoppingcarts/productitems/ProductItem.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.productitems; 2 | 3 | import java.util.UUID; 4 | 5 | public record ProductItem( 6 | UUID productId, 7 | int quantity 8 | ) { 9 | public ProductItem { 10 | if (quantity <= 0) 11 | throw new IllegalArgumentException("Quantity has to be a positive number"); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/java/io/eventdriven/ecommerce/shoppingcarts/removingproductitem/RemoveProductItemFromShoppingCart.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.shoppingcarts.removingproductitem; 2 | 3 | import io.eventdriven.ecommerce.shoppingcarts.ShoppingCartEvent.ProductItemRemovedFromShoppingCart; 4 | import io.eventdriven.ecommerce.shoppingcarts.ShoppingCart; 5 | import io.eventdriven.ecommerce.shoppingcarts.productitems.PricedProductItem; 6 | 7 | import java.util.UUID; 8 | 9 | public record RemoveProductItemFromShoppingCart( 10 | UUID shoppingCartId, 11 | PricedProductItem productItem, 12 | Long expectedVersion 13 | ) { 14 | public static ProductItemRemovedFromShoppingCart handle( 15 | RemoveProductItemFromShoppingCart command, 16 | ShoppingCart shoppingCart 17 | ) { 18 | if (shoppingCart.isClosed()) 19 | throw new IllegalStateException("Adding product item for cart in '%s' status is not allowed.".formatted(shoppingCart.status())); 20 | 21 | shoppingCart.productItems().assertThatCanRemove(command.productItem()); 22 | 23 | return new ProductItemRemovedFromShoppingCart( 24 | command.shoppingCartId, 25 | command.productItem() 26 | ); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.jpa.database=POSTGRESQL 2 | spring.sql.init.platform= postgres 3 | spring.datasource.url=jdbc:postgresql://localhost:5432/postgres 4 | spring.datasource.username=postgres 5 | spring.datasource.password=Password12! 6 | spring.jpa.show-sql=true 7 | spring.jpa.generate-ddl=true 8 | spring.jpa.hibernate.ddl-auto=update 9 | spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true 10 | esdb.connectionstring=esdb://localhost:2113?tls=false 11 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/resources/log4j2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | %d %p %c{1.} [%t] %m%n 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/resources/schema-postgres.sql: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/CQRS_Flow/Java/event-sourcing-esdb-simple/src/main/resources/schema-postgres.sql -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/test/java/io/eventdriven/ecommerce/api/controller/OpenShoppingCartTests.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.api.controller; 2 | 3 | import io.eventdriven.ecommerce.ECommerceApplication; 4 | import io.eventdriven.ecommerce.api.requests.ShoppingCartsRequests; 5 | import io.eventdriven.ecommerce.testing.ApiSpecification; 6 | import org.json.JSONObject; 7 | import org.junit.jupiter.api.Test; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | 10 | import java.util.UUID; 11 | 12 | import static io.eventdriven.ecommerce.testing.HttpEntityUtils.toHttpEntity; 13 | 14 | @SpringBootTest(classes = ECommerceApplication.class, 15 | webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) 16 | public class OpenShoppingCartTests extends ApiSpecification { 17 | public OpenShoppingCartTests() { 18 | super("api/shopping-carts"); 19 | } 20 | 21 | @Test 22 | public void openShoppingCart_succeeds_forValidData() { 23 | given(() -> new ShoppingCartsRequests.Open(UUID.randomUUID())) 24 | .when(POST) 25 | .then(CREATED); 26 | } 27 | 28 | @Test 29 | public void openShoppingCart_fails_withBadRequest_forInvalidBody() { 30 | given(() -> toHttpEntity(new JSONObject())) 31 | .when(POST) 32 | .then(BAD_REQUEST); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CQRS_Flow/Java/event-sourcing-esdb-simple/src/test/java/io/eventdriven/ecommerce/testing/HttpEntityUtils.java: -------------------------------------------------------------------------------- 1 | package io.eventdriven.ecommerce.testing; 2 | 3 | import org.springframework.http.HttpEntity; 4 | import org.springframework.http.HttpHeaders; 5 | import org.springframework.http.MediaType; 6 | 7 | public final class HttpEntityUtils { 8 | public static HttpEntity toHttpEntity(org.json.JSONObject jsonBody){ 9 | var headers = new HttpHeaders(); 10 | headers.setContentType(MediaType.APPLICATION_JSON); 11 | 12 | return new HttpEntity<>(jsonBody.toString(), headers); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Crypto_Shredding/Dotnet/CryptoShredding.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb_AaBb" /> 3 | True 4 | True 5 | True 6 | True 7 | True 8 | True 9 | True 10 | True 11 | True 12 | True -------------------------------------------------------------------------------- /Crypto_Shredding/Dotnet/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | services: 3 | ####################################################### 4 | # EventStoreDB - Event Store 5 | ####################################################### 6 | eventstore.db: 7 | image: eventstore/eventstore:21.10.0-buster-slim 8 | # use this image if you're running ARM-based proc like Apple M1 9 | # image: ghcr.io/eventstore/eventstore:21.10.0-alpha-arm64v8 10 | environment: 11 | - EVENTSTORE_CLUSTER_SIZE=1 12 | - EVENTSTORE_RUN_PROJECTIONS=All 13 | - EVENTSTORE_START_STANDARD_PROJECTIONS=true 14 | - EVENTSTORE_EXT_TCP_PORT=1113 15 | - EVENTSTORE_HTTP_PORT=2113 16 | - EVENTSTORE_INSECURE=true 17 | - EVENTSTORE_ENABLE_EXTERNAL_TCP=true 18 | - EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true 19 | ports: 20 | - '1113:1113' 21 | - '2113:2113' 22 | volumes: 23 | - type: volume 24 | source: eventstore-volume-data 25 | target: /var/lib/eventstore 26 | - type: volume 27 | source: eventstore-volume-logs 28 | target: /var/log/eventstore 29 | networks: 30 | - eventstore.db 31 | 32 | networks: 33 | eventstore.db: 34 | driver: bridge 35 | 36 | volumes: 37 | eventstore-volume-data: 38 | eventstore-volume-logs: 39 | -------------------------------------------------------------------------------- /Crypto_Shredding/Dotnet/src/CryptoShredding.IntegrationTests/CryptoShredding.IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | all 15 | runtime; build; native; contentfiles; analyzers 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Crypto_Shredding/Dotnet/src/CryptoShredding.IntegrationTests/TestSupport/Given_When_Then.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace CryptoShredding.IntegrationTests.TestSupport; 5 | 6 | public abstract class Given_WhenAsync_Then_Test 7 | : IDisposable 8 | { 9 | protected Given_WhenAsync_Then_Test() 10 | { 11 | Task.Run((Func) (async () => await this.SetupAsync())).Wait(); 12 | } 13 | 14 | private async Task SetupAsync() 15 | { 16 | await Given(); 17 | await When(); 18 | } 19 | 20 | protected abstract Task Given(); 21 | 22 | protected abstract Task When(); 23 | 24 | public void Dispose() 25 | { 26 | Cleanup(); 27 | } 28 | 29 | protected virtual void Cleanup() 30 | { 31 | } 32 | } -------------------------------------------------------------------------------- /Crypto_Shredding/Dotnet/src/CryptoShredding/Attributes/DataSubjectIdAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CryptoShredding.Attributes; 4 | 5 | /** 6 | * Specifies the PII owner (e.g: the person Id) 7 | */ 8 | public class DataSubjectIdAttribute 9 | : Attribute 10 | { 11 | } -------------------------------------------------------------------------------- /Crypto_Shredding/Dotnet/src/CryptoShredding/Attributes/PersonalDataAttribute.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CryptoShredding.Attributes; 4 | 5 | /** 6 | * Specifies the property that holds PII 7 | */ 8 | public class PersonalDataAttribute 9 | : Attribute 10 | { 11 | } -------------------------------------------------------------------------------- /Crypto_Shredding/Dotnet/src/CryptoShredding/Contracts/IEvent.cs: -------------------------------------------------------------------------------- 1 | namespace CryptoShredding.Contracts; 2 | 3 | public interface IEvent 4 | { 5 | } -------------------------------------------------------------------------------- /Crypto_Shredding/Dotnet/src/CryptoShredding/CryptoShredding.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Crypto_Shredding/Dotnet/src/CryptoShredding/EventConverter.cs: -------------------------------------------------------------------------------- 1 | using CryptoShredding.Contracts; 2 | using CryptoShredding.Serialization; 3 | using EventStore.Client; 4 | 5 | namespace CryptoShredding; 6 | 7 | public class EventConverter 8 | { 9 | private readonly JsonSerializer _jsonSerializer; 10 | 11 | public EventConverter(JsonSerializer jsonSerializer) 12 | { 13 | _jsonSerializer = jsonSerializer; 14 | } 15 | 16 | public IEvent ToEvent(ResolvedEvent resolvedEvent) 17 | { 18 | var data = resolvedEvent.Event.Data; 19 | var metadata = resolvedEvent.Event.Metadata; 20 | var eventName = resolvedEvent.Event.EventType; 21 | var persistableEvent = _jsonSerializer.Deserialize(data, metadata, eventName); 22 | return persistableEvent; 23 | } 24 | 25 | public EventData ToEventData(IEvent @event) 26 | { 27 | var eventTypeName = @event.GetType().Name; 28 | var id = Uuid.NewUuid(); 29 | var serializedEvent = _jsonSerializer.Serialize(@event); 30 | var contentType = serializedEvent.IsJson ? "application/json" : "application/octet-stream"; 31 | var data = serializedEvent.Data; 32 | var metadata = serializedEvent.MetaData; 33 | var eventData = new EventData(id, eventTypeName,data, metadata, contentType); 34 | return eventData; 35 | } 36 | } -------------------------------------------------------------------------------- /Crypto_Shredding/Dotnet/src/CryptoShredding/Repository/CryptoRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace CryptoShredding.Repository; 5 | 6 | public class CryptoRepository 7 | { 8 | private readonly IDictionary _cryptoStore; 9 | 10 | public CryptoRepository() 11 | { 12 | _cryptoStore = new Dictionary(); 13 | } 14 | 15 | public EncryptionKey GetExistingOrNew(string id, Func keyGenerator) 16 | { 17 | var isExisting = _cryptoStore.TryGetValue(id, out var keyStored); 18 | if (isExisting) 19 | { 20 | return keyStored; 21 | } 22 | 23 | var newEncryptionKey = keyGenerator.Invoke(); 24 | _cryptoStore.Add(id, newEncryptionKey); 25 | return newEncryptionKey; 26 | } 27 | 28 | public EncryptionKey GetExistingOrDefault(string id) 29 | { 30 | var isExisting = _cryptoStore.TryGetValue(id, out var keyStored); 31 | if (isExisting) 32 | { 33 | return keyStored; 34 | } 35 | 36 | return default; 37 | } 38 | 39 | public void DeleteEncryptionKey(string id) 40 | { 41 | _cryptoStore.Remove(id); 42 | } 43 | } -------------------------------------------------------------------------------- /Crypto_Shredding/Dotnet/src/CryptoShredding/Repository/EncryptionKey.cs: -------------------------------------------------------------------------------- 1 | namespace CryptoShredding.Repository; 2 | 3 | public class EncryptionKey 4 | { 5 | public byte[] Key { get; } 6 | public byte[] Nonce { get; } 7 | 8 | public EncryptionKey( 9 | byte[] key, 10 | byte[] nonce) 11 | { 12 | Key = key; 13 | Nonce = nonce; 14 | } 15 | } -------------------------------------------------------------------------------- /Crypto_Shredding/Dotnet/src/CryptoShredding/Serialization/EncryptorDecryptor.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Cryptography; 2 | using CryptoShredding.Repository; 3 | 4 | namespace CryptoShredding.Serialization; 5 | 6 | public class EncryptorDecryptor 7 | { 8 | private readonly CryptoRepository _cryptoRepository; 9 | 10 | public EncryptorDecryptor(CryptoRepository cryptoRepository) 11 | { 12 | _cryptoRepository = cryptoRepository; 13 | } 14 | 15 | public ICryptoTransform GetEncryptor(string dataSubjectId) 16 | { 17 | var encryptionKey = _cryptoRepository.GetExistingOrNew(dataSubjectId, CreateNewEncryptionKey); 18 | var aes = GetAes(encryptionKey); 19 | var encryptor = aes.CreateEncryptor(); 20 | return encryptor; 21 | } 22 | 23 | public ICryptoTransform GetDecryptor(string dataSubjectId) 24 | { 25 | var encryptionKey = _cryptoRepository.GetExistingOrDefault(dataSubjectId); 26 | if (encryptionKey is null) 27 | { 28 | // encryption key was deleted 29 | return default; 30 | } 31 | 32 | var aes = GetAes(encryptionKey); 33 | var decryptor = aes.CreateDecryptor(); 34 | return decryptor; 35 | } 36 | 37 | private EncryptionKey CreateNewEncryptionKey() 38 | { 39 | var aes = Aes.Create(); 40 | 41 | aes.Padding = PaddingMode.PKCS7; 42 | 43 | var key = aes.Key; 44 | var nonce = aes.IV; 45 | 46 | var encryptionKey = new EncryptionKey(key, nonce); 47 | return encryptionKey; 48 | } 49 | 50 | private Aes GetAes(EncryptionKey encryptionKey) 51 | { 52 | var aes = Aes.Create(); 53 | 54 | aes.Padding = PaddingMode.PKCS7; 55 | aes.Key = encryptionKey.Key; 56 | aes.IV = encryptionKey.Nonce; 57 | 58 | return aes; 59 | } 60 | } -------------------------------------------------------------------------------- /Crypto_Shredding/Dotnet/src/CryptoShredding/Serialization/JsonConverters/DecryptionJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | using Newtonsoft.Json; 4 | 5 | namespace CryptoShredding.Serialization.JsonConverters; 6 | 7 | public class DecryptionJsonConverter 8 | : JsonConverter 9 | { 10 | private readonly ICryptoTransform _decryptor; 11 | private readonly FieldEncryptionDecryption _fieldEncryptionDecryption; 12 | 13 | public DecryptionJsonConverter( 14 | ICryptoTransform decryptor, 15 | FieldEncryptionDecryption fieldEncryptionService) 16 | { 17 | _decryptor = decryptor; 18 | _fieldEncryptionDecryption = fieldEncryptionService; 19 | } 20 | 21 | public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) 22 | { 23 | throw new NotImplementedException(); 24 | } 25 | 26 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) 27 | { 28 | var value = reader.Value; 29 | var result = _fieldEncryptionDecryption.GetDecryptedOrDefault(value, _decryptor, objectType); 30 | return result; 31 | } 32 | 33 | public override bool CanConvert(Type objectType) 34 | { 35 | return true; 36 | } 37 | 38 | public override bool CanRead => true; 39 | public override bool CanWrite => false; 40 | } -------------------------------------------------------------------------------- /Crypto_Shredding/Dotnet/src/CryptoShredding/Serialization/JsonConverters/EncryptionJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Security.Cryptography; 3 | using Newtonsoft.Json; 4 | 5 | namespace CryptoShredding.Serialization.JsonConverters; 6 | 7 | public class EncryptionJsonConverter 8 | : JsonConverter 9 | { 10 | private readonly ICryptoTransform _encryptor; 11 | private readonly FieldEncryptionDecryption _fieldEncryptionDecryption; 12 | 13 | public EncryptionJsonConverter( 14 | ICryptoTransform encryptor, 15 | FieldEncryptionDecryption fieldEncryptionDecryption) 16 | { 17 | _encryptor = encryptor; 18 | _fieldEncryptionDecryption = fieldEncryptionDecryption; 19 | } 20 | 21 | public override void WriteJson(JsonWriter writer, object value, Newtonsoft.Json.JsonSerializer serializer) 22 | { 23 | var result = _fieldEncryptionDecryption.GetEncryptedOrDefault(value, _encryptor); 24 | writer.WriteValue(result); 25 | } 26 | 27 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, Newtonsoft.Json.JsonSerializer serializer) 28 | { 29 | throw new NotImplementedException(); 30 | } 31 | 32 | public override bool CanConvert(Type objectType) 33 | { 34 | return true; 35 | } 36 | 37 | public override bool CanRead => false; 38 | 39 | public override bool CanWrite => true; 40 | } -------------------------------------------------------------------------------- /Crypto_Shredding/Dotnet/src/CryptoShredding/Serialization/SerializedEvent.cs: -------------------------------------------------------------------------------- 1 | namespace CryptoShredding.Serialization; 2 | 3 | public class SerializedEvent 4 | { 5 | public byte[] Data { get; } 6 | public byte[] MetaData { get; } 7 | public bool IsJson { get; } 8 | 9 | public SerializedEvent(byte[] data, byte[] metaData, bool isJson) 10 | { 11 | Data = data; 12 | MetaData = metaData; 13 | IsJson = isJson; 14 | } 15 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 EventStore Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LoanApplication/Python/Projection-LoansApprovedDenied.js: -------------------------------------------------------------------------------- 1 | options({ 2 | $includeLinks: true, 3 | reorderEvents: false 4 | }) 5 | 6 | fromStreams(["$ce-loanRequest"]) 7 | .when({ 8 | "LoanAutomaticallyApproved": function(s, e) { 9 | linkTo("ApprovedLoans", e, e.metadata); 10 | }, 11 | "LoanManuallyApproved": function(s, e) { 12 | linkTo("ApprovedLoans", e, e.metadata); 13 | }, 14 | "LoanAutomaticallyDenied": function(s, e) { 15 | linkTo("DeniedLoans", e, e.metadata); 16 | }, 17 | "LoanManuallyDenied": function(s, e) { 18 | linkTo("DeniedLoans", e, e.metadata); 19 | } 20 | }); -------------------------------------------------------------------------------- /LoanApplication/Python/Projection-LoansByCountryName.js: -------------------------------------------------------------------------------- 1 | options({ 2 | $includeLinks: true, 3 | reorderEvents: false 4 | }) 5 | 6 | fromCategory("loanRequest") 7 | .when({ 8 | "LoanRequested": function(s, e) { 9 | var country = e.data.Address.Country; 10 | linkTo("Loans-" + country, e, e.metadata); 11 | } 12 | }); -------------------------------------------------------------------------------- /LoanApplication/Python/README.md: -------------------------------------------------------------------------------- 1 | # Loans Application - Python Sample Code 2 | 3 | Welcome to the Loans Application sample repo. This repo contains the python implementation of the Loans Project 4 | 5 | ## To get started 6 | 7 | * Install python version 3.11 or greater 8 | * Install the required packages: 9 | 10 | ``` 11 | pip install esdbclient 12 | pip install wxPython 13 | ``` 14 | 15 | If the above fails, try: 16 | 17 | ``` 18 | pip3 install esdbclient 19 | pip3 install wxPython 20 | ``` 21 | 22 | * Either clone the repository or download the code ZIP file 23 | * Install VSCode: https://code.visualstudio.com/download 24 | * Install Docker: https://www.docker.com/products/docker-desktop/ 25 | * Start the Docker image for your platform: https://www.eventstore.com/downloads/23.10 26 | * In your web browser, navigate to http://localhost:2113/web/index.html#/projections 27 | * Click the `Enable All` button to enable the system projections 28 | 29 | That's it! You're ready to go! 30 | 31 | ## Basic usage 32 | 33 | To start the project and see events move through the system, open four terminal windows. Run one of the following commands in each window: 34 | 35 | ``` 36 | python Underwriting.py 37 | python LoanDecider.py 38 | python CreditCheck.py 39 | python LoanRequestor-testCases.py 40 | ``` 41 | 42 | If the above commands fail, try: 43 | 44 | ``` 45 | python3 Underwriting.py 46 | python3 LoanDecider.py 47 | python3 CreditCheck.py 48 | python3 LoanRequestor-testCases.py 49 | ``` 50 | 51 | The last command, `LoanRequestor-testCases.py`, will inject some test loan requests into the system. You can instead use the command-line injector to create and append loan requests: 52 | 53 | ``` 54 | python LoanRequestor-commandLine.py 55 | ``` 56 | 57 | Or: 58 | 59 | ``` 60 | python3 LoanRequestor-commandLine.py 61 | ``` 62 | 63 | Check out our QuickStart video series on YouTube to see how everything works -------------------------------------------------------------------------------- /LoanApplication/Python/create_projections.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | curl -X POST 'https://localhost:2113/projections/continuous?name=LoansApprovedDenied&emit=yes&checkpoints=yes&enabled=yes&trackemittedstreams=yes' \ 4 | -v \ 5 | -k \ 6 | -i \ 7 | -u "admin:changeit" \ 8 | -H "Content-Type: application/json" \ 9 | --data-binary "@Projection-LoansApprovedDenied.js" 10 | 11 | curl -X POST 'https://localhost:2113/projections/continuous?name=LoansByCountryName&emit=yes&checkpoints=yes&enabled=yes&trackemittedstreams=yes' \ 12 | -v \ 13 | -k \ 14 | -i \ 15 | -u "admin:changeit" \ 16 | -H "Content-Type: application/json" \ 17 | --data-binary "@Projection-LoansByCountryName.js" 18 | -------------------------------------------------------------------------------- /LoanApplication/Python/utils.py: -------------------------------------------------------------------------------- 1 | import config 2 | import esdbclient 3 | import logging 4 | import ssl 5 | from esdbclient.connection_spec import ConnectionSpec 6 | 7 | log = logging.getLogger(__name__) 8 | 9 | def create_db_client() -> esdbclient.EventStoreDBClient: 10 | log.info(f"Connecting to the database: url={config.ESDB_URL}") 11 | spec = ConnectionSpec(config.ESDB_URL) 12 | server_certificate = None 13 | if spec.options.Tls: 14 | target = spec.targets[0] 15 | target_coordinates = target.split(":") 16 | log.info(f"Database TLS is enabled, fetching certificate details: host={target_coordinates[0]}") 17 | server_certificate = ssl.get_server_certificate(addr=(target_coordinates[0], int(target_coordinates[1]))) 18 | log.info(f"Database TLS is enabled, fetching certificate details...done.") 19 | return esdbclient.EventStoreDBClient(uri=config.ESDB_URL, root_certificates=server_certificate) -------------------------------------------------------------------------------- /LoanApplication/docker-compose.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | seeder: 3 | restart: always 4 | image: "docker.eventstore.com/education/loan-application:0.1.0" 5 | environment: 6 | - "DB_URL=${DB_URL?Error: DB_URL must be specified!}" 7 | command: ./shadowtraffic.sh 8 | creditcheck: 9 | restart: always 10 | image: "docker.eventstore.com/education/loan-application:0.1.0" 11 | environment: 12 | - "ESDB_URL=${DB_URL?Error: DB_URL must be specified!}" 13 | command: python3 CreditCheck.py 14 | decider: 15 | restart: always 16 | image: "docker.eventstore.com/education/loan-application:0.1.0" 17 | environment: 18 | - "ESDB_URL=${DB_URL?Error: DB_URL must be specified!}" 19 | command: python3 LoanDecider.py 20 | underwriter: 21 | restart: always 22 | image: "docker.eventstore.com/education/loan-application:0.1.0" 23 | environment: 24 | - "ESDB_URL=${DB_URL?Error: DB_URL must be specified!}" 25 | command: python3 Underwriting.py 26 | -------------------------------------------------------------------------------- /LoanApplication/images/image1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image1.png -------------------------------------------------------------------------------- /LoanApplication/images/image10x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image10x.png -------------------------------------------------------------------------------- /LoanApplication/images/image11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image11.png -------------------------------------------------------------------------------- /LoanApplication/images/image11x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image11x.png -------------------------------------------------------------------------------- /LoanApplication/images/image12x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image12x.png -------------------------------------------------------------------------------- /LoanApplication/images/image13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image13.png -------------------------------------------------------------------------------- /LoanApplication/images/image14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image14.png -------------------------------------------------------------------------------- /LoanApplication/images/image15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image15.png -------------------------------------------------------------------------------- /LoanApplication/images/image16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image16.png -------------------------------------------------------------------------------- /LoanApplication/images/image17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image17.png -------------------------------------------------------------------------------- /LoanApplication/images/image18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image18.png -------------------------------------------------------------------------------- /LoanApplication/images/image2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image2.png -------------------------------------------------------------------------------- /LoanApplication/images/image3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image3.png -------------------------------------------------------------------------------- /LoanApplication/images/image4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image4.png -------------------------------------------------------------------------------- /LoanApplication/images/image5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image5.png -------------------------------------------------------------------------------- /LoanApplication/images/image6x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image6x.png -------------------------------------------------------------------------------- /LoanApplication/images/image7x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image7x.png -------------------------------------------------------------------------------- /LoanApplication/images/image8x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image8x.png -------------------------------------------------------------------------------- /LoanApplication/images/image9x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/LoanApplication/images/image9x.png -------------------------------------------------------------------------------- /Logging/Elastic/Filebeat/filebeat.yml: -------------------------------------------------------------------------------- 1 | ####################################################### 2 | # EventStoreDB logs file input 3 | ####################################################### 4 | filebeat.inputs: 5 | - type: log 6 | paths: 7 | - /var/log/eventstore/*/log*.json 8 | json.keys_under_root: true 9 | json.add_error_key: true 10 | 11 | ####################################################### 12 | # ElasticSearch direct output 13 | ####################################################### 14 | output.elasticsearch: 15 | index: "eventstoredb-%{[agent.version]}" 16 | hosts: ["elasticsearch:9200"] 17 | 18 | ####################################################### 19 | # ElasticSearch dashboard configuration 20 | # (index pattern and data view) 21 | ####################################################### 22 | setup.dashboards: 23 | enabled: true 24 | index: "eventstoredb-*" 25 | 26 | setup.template: 27 | name: "eventstoredb" 28 | pattern: "eventstoredb-%{[agent.version]}" 29 | 30 | ####################################################### 31 | # Kibana dashboard configuration 32 | ####################################################### 33 | setup.kibana: 34 | host: "kibana:5601" 35 | -------------------------------------------------------------------------------- /Logging/Elastic/Filebeat/logs/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/Logging/Elastic/Filebeat/logs/.gitignore -------------------------------------------------------------------------------- /Logging/Elastic/FilebeatWithLogstash/filebeat.yml: -------------------------------------------------------------------------------- 1 | ####################################################### 2 | # EventStoreDB logs file input 3 | ####################################################### 4 | filebeat.inputs: 5 | - type: log 6 | paths: 7 | - /var/log/eventstore/*/log*.json 8 | json.keys_under_root: true 9 | json.add_error_key: true 10 | 11 | ####################################################### 12 | # Logstash output to transform and prepare logs 13 | ####################################################### 14 | output.logstash: 15 | hosts: ["logstash:5044"] 16 | -------------------------------------------------------------------------------- /Logging/Elastic/FilebeatWithLogstash/logs/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/Logging/Elastic/FilebeatWithLogstash/logs/.gitignore -------------------------------------------------------------------------------- /Logging/Elastic/FilebeatWithLogstash/logstash.conf: -------------------------------------------------------------------------------- 1 | ####################################################### 2 | # Filebeat input 3 | ####################################################### 4 | input { 5 | beats { 6 | port => 5044 7 | } 8 | } 9 | 10 | ####################################################### 11 | # Filter out stats from regular logs 12 | # add respecting field with log type 13 | ####################################################### 14 | filter { 15 | # check if log path includes "log-stats" 16 | # so pattern for stats 17 | if [log][file][path] =~ "log-stats" { 18 | mutate { 19 | add_field => { 20 | "log_type" => "stats" 21 | } 22 | } 23 | } 24 | else { 25 | mutate { 26 | add_field => { 27 | "log_type" => "logs" 28 | } 29 | } 30 | } 31 | } 32 | 33 | ####################################################### 34 | # Send logs to Elastic 35 | # Create separate indexes for stats and regular logs 36 | # using field defined in the filter transformation 37 | ####################################################### 38 | output { 39 | elasticsearch { 40 | hosts => [ "elasticsearch:9200" ] 41 | index => 'eventstoredb-%{[log_type]}' 42 | } 43 | } -------------------------------------------------------------------------------- /Logging/Elastic/Logstash/logs/.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kurrent-io/samples/700ff662f57eb64ab28469ea1f46156c98612a7a/Logging/Elastic/Logstash/logs/.gitignore -------------------------------------------------------------------------------- /Logging/Elastic/Logstash/logstash.conf: -------------------------------------------------------------------------------- 1 | 2 | ####################################################### 3 | # EventStoreDB logs file input 4 | ####################################################### 5 | input { 6 | file { 7 | path => "/var/log/eventstore/*/log*.json" 8 | start_position => "beginning" 9 | codec => json 10 | } 11 | } 12 | 13 | ####################################################### 14 | # Filter out stats from regular logs 15 | # add respecting field with log type 16 | ####################################################### 17 | filter { 18 | # check if log path includes "log-stats" 19 | # so pattern for stats 20 | if [log][file][path] =~ "log-stats" { 21 | mutate { 22 | add_field => { 23 | "log_type" => "stats" 24 | } 25 | } 26 | } 27 | else { 28 | mutate { 29 | add_field => { 30 | "log_type" => "logs" 31 | } 32 | } 33 | } 34 | } 35 | 36 | ####################################################### 37 | # Send logs to Elastic 38 | # Create separate indexes for stats and regular logs 39 | # using field defined in the filter transformation 40 | ####################################################### 41 | output { 42 | elasticsearch { 43 | hosts => [ "elasticsearch:9200" ] 44 | index => 'eventstoredb-%{[log_type]}' 45 | } 46 | } -------------------------------------------------------------------------------- /Logging/Elastic/README.md: -------------------------------------------------------------------------------- 1 | # Sending EventStoreDB logs to Elasticsearch 2 | 3 | Elastic Stack is one of the most popular tools for ingesting and analyzing logs and statistics: 4 | - [Elasticsearch](https://www.elastic.co/guide/en/elasticsearch/reference/8.2/index.html) was built for advanced filtering and text analysis. 5 | - [Filebeat](https://www.elastic.co/guide/en/beats/filebeat/8.2/index.html) allow tailing files efficiently. 6 | - [Logstash](https://www.elastic.co/guide/en/logstash/current/getting-started-with-logstash.html) enables log transformations and processing pipelines. 7 | - [Kibana](https://www.elastic.co/guide/en/kibana/8.2/index.html) is a dashboard and visualization UI for Elasticsearch data. 8 | 9 | EventStoreDB exposes structured information through its logs and statistics, allowing straightforward integration with mentioned tooling. 10 | 11 | This samples show how to configure various ways of sending logs from EventStoreDB to Elasticsearch: 12 | - [Logstash](./Logstash/), 13 | - [Filebeat](./Filebeat/), 14 | - [FilebeatWithLogstash](./FilebeatWithLogstash/) 15 | 16 | **DISCLAIMER: Configurations in samples are presented as docker-compose to simplify the developer environment setup. It aims to give the quick option to play with Elastic setup. It's NOT recommended to run setup through docker-compose on production. You should follow the [EventStoreDB installation guide](https://developers.eventstore.com/server/v21.10/installation.html) and [Elastic documentation](https://www.elastic.co/guide/index.html).** -------------------------------------------------------------------------------- /Quickstart/Dotnet/esdb-sample-dotnet/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.env 3 | **/.git 4 | **/.gitignore 5 | **/.project 6 | **/.settings 7 | **/.toolstarget 8 | **/.vs 9 | **/.vscode 10 | **/.idea 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /Quickstart/Dotnet/esdb-sample-dotnet/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base 2 | USER $APP_UID 3 | WORKDIR /app 4 | 5 | FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build 6 | ARG BUILD_CONFIGURATION=Release 7 | WORKDIR /src 8 | COPY ["EventStoreDB.Dotnet.Sample.csproj", "./"] 9 | RUN dotnet restore "EventStoreDB.Dotnet.Sample.csproj" 10 | COPY . . 11 | WORKDIR "/src/" 12 | RUN dotnet build "EventStoreDB.Dotnet.Sample.csproj" -c $BUILD_CONFIGURATION -o /app/build 13 | 14 | FROM build AS publish 15 | ARG BUILD_CONFIGURATION=Release 16 | RUN dotnet publish "EventStoreDB.Dotnet.Sample.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false 17 | 18 | FROM base AS final 19 | WORKDIR /app 20 | COPY --from=publish /app/publish . 21 | EXPOSE 8080 22 | ENTRYPOINT ["dotnet", "EventStoreDB.Dotnet.Sample.dll"] 23 | -------------------------------------------------------------------------------- /Quickstart/Dotnet/esdb-sample-dotnet/EventStoreDB.Dotnet.Sample.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | true 8 | Linux 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Quickstart/Dotnet/esdb-sample-dotnet/EventStoreDB.Dotnet.Sample.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EventStoreDB.Dotnet.Sample", "EventStoreDB.Dotnet.Sample.csproj", "{2BFA3FFA-488C-4F20-BA3C-B603F5E75CC5}" 4 | EndProject 5 | Global 6 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 7 | Debug|Any CPU = Debug|Any CPU 8 | Release|Any CPU = Release|Any CPU 9 | EndGlobalSection 10 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 11 | {2BFA3FFA-488C-4F20-BA3C-B603F5E75CC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 12 | {2BFA3FFA-488C-4F20-BA3C-B603F5E75CC5}.Debug|Any CPU.Build.0 = Debug|Any CPU 13 | {2BFA3FFA-488C-4F20-BA3C-B603F5E75CC5}.Release|Any CPU.ActiveCfg = Release|Any CPU 14 | {2BFA3FFA-488C-4F20-BA3C-B603F5E75CC5}.Release|Any CPU.Build.0 = Release|Any CPU 15 | EndGlobalSection 16 | EndGlobal 17 | -------------------------------------------------------------------------------- /Quickstart/Dotnet/esdb-sample-dotnet/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:59493", 8 | "sslPort": 44320 9 | } 10 | }, 11 | "profiles": { 12 | "http": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "http://localhost:8080" 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Quickstart/Dotnet/esdb-sample-dotnet/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /Quickstart/Dotnet/esdb-sample-dotnet/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | esdb-sample-dotnet: 3 | image: esdb-sample-dotnet 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | ports: 8 | - "8080:8080" 9 | networks: 10 | - esdb-local-net 11 | depends_on: 12 | esdb-local: 13 | condition: service_started 14 | esdb-local: 15 | image: eventstore/eventstore:latest 16 | environment: 17 | EVENTSTORE_INSECURE: true 18 | ports: 19 | - "2113:2113" 20 | networks: 21 | - esdb-local-net 22 | networks: 23 | esdb-local-net: -------------------------------------------------------------------------------- /Quickstart/Go/esdb-sample-go/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.git 3 | **/.gitignore 4 | **/docker-compose* 5 | **/Dockerfile* 6 | LICENSE 7 | README.md -------------------------------------------------------------------------------- /Quickstart/Go/esdb-sample-go/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:1.21 2 | WORKDIR /usr/src/app 3 | COPY go.mod go.sum ./ 4 | RUN go mod download && go mod verify 5 | COPY . . 6 | RUN go build -v -o /usr/local/bin/esdb-sample-go ./... 7 | ENTRYPOINT ["esdb-sample-go"] -------------------------------------------------------------------------------- /Quickstart/Go/esdb-sample-go/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | esdb-sample-go: 3 | image: esdb-sample-go 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | ports: 8 | - "8080:8080" 9 | networks: 10 | - esdb-local-net 11 | depends_on: 12 | esdb-local: 13 | condition: service_started 14 | esdb-local: 15 | image: eventstore/eventstore:latest 16 | environment: 17 | EVENTSTORE_INSECURE: true 18 | ports: 19 | - "2113:2113" 20 | networks: 21 | - esdb-local-net 22 | networks: 23 | esdb-local-net: -------------------------------------------------------------------------------- /Quickstart/Go/esdb-sample-go/go.mod: -------------------------------------------------------------------------------- 1 | module main 2 | 3 | go 1.21.0 4 | 5 | require ( 6 | github.com/EventStore/EventStore-Client-Go/v3 v3.2.1 // indirect 7 | github.com/bytedance/sonic v1.9.1 // indirect 8 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 9 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 10 | github.com/gin-contrib/sse v0.1.0 // indirect 11 | github.com/gin-gonic/gin v1.9.1 // indirect 12 | github.com/go-playground/locales v0.14.1 // indirect 13 | github.com/go-playground/universal-translator v0.18.1 // indirect 14 | github.com/go-playground/validator/v10 v10.14.0 // indirect 15 | github.com/goccy/go-json v0.10.2 // indirect 16 | github.com/gofrs/uuid v4.4.0+incompatible // indirect 17 | github.com/golang/protobuf v1.5.3 // indirect 18 | github.com/json-iterator/go v1.1.12 // indirect 19 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 20 | github.com/leodido/go-urn v1.2.4 // indirect 21 | github.com/mattn/go-isatty v0.0.19 // indirect 22 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 23 | github.com/modern-go/reflect2 v1.0.2 // indirect 24 | github.com/pelletier/go-toml/v2 v2.0.8 // indirect 25 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 26 | github.com/ugorji/go/codec v1.2.11 // indirect 27 | golang.org/x/arch v0.3.0 // indirect 28 | golang.org/x/crypto v0.14.0 // indirect 29 | golang.org/x/net v0.17.0 // indirect 30 | golang.org/x/sys v0.13.0 // indirect 31 | golang.org/x/text v0.13.0 // indirect 32 | google.golang.org/genproto/googleapis/rpc v0.0.0-20231016165738-49dd2c1f3d0b // indirect 33 | google.golang.org/grpc v1.59.0 // indirect 34 | google.golang.org/protobuf v1.31.0 // indirect 35 | gopkg.in/yaml.v3 v3.0.1 // indirect 36 | ) 37 | -------------------------------------------------------------------------------- /Quickstart/Java/esdb-sample-springboot/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.git 3 | **/.gitignore 4 | **/docker-compose* 5 | **/Dockerfile* 6 | **/target/ 7 | **/build/ 8 | LICENSE 9 | README.md -------------------------------------------------------------------------------- /Quickstart/Java/esdb-sample-springboot/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gradle:7.6.3-jdk8 AS build 2 | COPY --chown=gradle:gradle . /home/gradle/src 3 | WORKDIR /home/gradle/src 4 | RUN gradle build --no-daemon 5 | 6 | FROM openjdk:8-jre-slim 7 | RUN mkdir /app 8 | COPY --from=build /home/gradle/src/build/libs/*.jar /app/esdb-sample-springboot.jar 9 | EXPOSE 8080 10 | ENTRYPOINT ["java", "-jar", "/app/esdb-sample-springboot.jar"] -------------------------------------------------------------------------------- /Quickstart/Java/esdb-sample-springboot/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | // Java 17 3 | // id 'org.springframework.boot' version '3.0.0' 4 | // id 'io.spring.dependency-management' version '1.1.0' 5 | // Java 8 6 | id 'org.springframework.boot' version '2.7.11' 7 | id 'io.spring.dependency-management' version '1.0.15.RELEASE' 8 | 9 | id 'java' 10 | } 11 | 12 | group = 'com.example' 13 | version = '0.0.1' 14 | sourceCompatibility = '1.8' // '17' 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | implementation 'org.springframework.boot:spring-boot-starter-web' 22 | implementation 'com.eventstore:db-client-java:5.3.0' 23 | implementation 'com.google.code.gson:gson:2.10.1' 24 | } 25 | 26 | // Java 17 27 | // test { 28 | // useJUnitPlatform() 29 | // } 30 | 31 | // Java 8 32 | tasks.named('test') { 33 | useJUnitPlatform() 34 | } 35 | 36 | -------------------------------------------------------------------------------- /Quickstart/Java/esdb-sample-springboot/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | esdb-sample-springboot: 3 | image: esdb-sample-springboot 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | ports: 8 | - "8080:8080" 9 | networks: 10 | - esdb-local-net 11 | depends_on: 12 | esdb-local: 13 | condition: service_started 14 | esdb-local: 15 | image: eventstore/eventstore:latest 16 | environment: 17 | EVENTSTORE_INSECURE: true 18 | ports: 19 | - "2113:2113" 20 | networks: 21 | - esdb-local-net 22 | networks: 23 | esdb-local-net: -------------------------------------------------------------------------------- /Quickstart/Java/esdb-sample-springboot/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Quickstart/Java/esdb-sample-springboot/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 9 | 10 | 11 | 2.7.11 12 | 13 | 14 | com.example.esdbsamplespringboot 15 | esdb-sample-springboot 16 | 0.0.1 17 | esdb-sample-springboot 18 | EventStoreDB Sample Spring Boot Application 19 | 20 | 21 | 1.8 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-web 27 | 28 | 29 | com.eventstore 30 | db-client-java 31 | 5.3.0 32 | 33 | 34 | com.google.code.gson 35 | gson 36 | 2.10.1 37 | 38 | 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-maven-plugin 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Quickstart/Java/esdb-sample-springboot/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'esdb-sample-springboot' 2 | -------------------------------------------------------------------------------- /Quickstart/Java/esdb-sample-springboot/src/main/java/com/example/esdbsamplespringboot/EventStoreDBConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.example.esdbsamplespringboot; 2 | 3 | import com.eventstore.dbclient.*; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | public class EventStoreDBConfiguration { 9 | @Bean 10 | public EventStoreDBClient EventStoreDBClient() { 11 | return EventStoreDBClient.create( 12 | EventStoreDBConnectionString.parseOrThrow( 13 | "esdb://admin:changeit@esdb-local:2113?tls=false&tlsVerifyCert=false")); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Quickstart/Java/esdb-sample-springboot/src/main/java/com/example/esdbsamplespringboot/HelloWorldApplication.java: -------------------------------------------------------------------------------- 1 | package com.example.esdbsamplespringboot; 2 | 3 | import com.google.gson.Gson; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.context.annotation.Bean; 7 | 8 | @SpringBootApplication 9 | public class HelloWorldApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(HelloWorldApplication.class, args); 12 | } 13 | 14 | @Bean 15 | public Gson GsonMapper() { 16 | return new Gson(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Quickstart/Java/esdb-sample-springboot/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port: 8080 2 | server.address: 0.0.0.0 3 | 4 | -------------------------------------------------------------------------------- /Quickstart/Java/esdb-sample-springboot/src/main/resources/log4j.properties: -------------------------------------------------------------------------------- 1 | log4j.rootLogger=INFO, STDOUT 2 | log4j.appender.STDOUT=org.apache.log4j.ConsoleAppender 3 | log4j.appender.STDOUT.layout=org.apache.log4j.PatternLayout 4 | log4j.appender.STDOUT.layout.ConversionPattern=%5p [%t] (%F:%L) - %m%n 5 | 6 | -------------------------------------------------------------------------------- /Quickstart/Nodejs/esdb-sample-nodejs/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.git 3 | **/.gitignore 4 | **/docker-compose* 5 | **/Dockerfile* 6 | **/node_modules 7 | **/npm-debug.log 8 | LICENSE 9 | README.md -------------------------------------------------------------------------------- /Quickstart/Nodejs/esdb-sample-nodejs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:slim 2 | WORKDIR /src 3 | COPY . . 4 | RUN npm install 5 | EXPOSE 8080 6 | CMD ["node", "app.js"] -------------------------------------------------------------------------------- /Quickstart/Nodejs/esdb-sample-nodejs/app.js: -------------------------------------------------------------------------------- 1 | const express = require('express') 2 | const app = express() 3 | const port = 8080 4 | 5 | const { 6 | EventStoreDBClient, 7 | jsonEvent, 8 | FORWARDS, 9 | START, 10 | } = require("@eventstore/db-client"); 11 | 12 | const eventStore = new EventStoreDBClient( 13 | { endpoint: 'esdb-local:2113' }, 14 | { insecure: true } 15 | ) 16 | 17 | const visitorsStream = 'visitors-stream' 18 | 19 | app.get('/hello-world', async (req, res) => { 20 | const visitor = req.query.visitor ?? 'Visitor' 21 | 22 | const event = jsonEvent({ 23 | type: 'VisitorGreeted', 24 | data: { 25 | visitor, 26 | }, 27 | }) 28 | 29 | await eventStore.appendToStream(visitorsStream, [event]) 30 | 31 | const eventStream = eventStore.readStream(visitorsStream, { 32 | fromRevision: START, 33 | direction: FORWARDS, 34 | }) 35 | 36 | let visitorsGreeted = [] 37 | for await (const { event } of eventStream) 38 | visitorsGreeted.push(event.data.visitor) 39 | 40 | res.send(`${visitorsGreeted.length} visitors have been greeted, they are: [${visitorsGreeted.join(',')}]`) 41 | }) 42 | 43 | app.listen(port, () => { 44 | console.log(`Sample app listening on port ${port}`) 45 | }) -------------------------------------------------------------------------------- /Quickstart/Nodejs/esdb-sample-nodejs/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | esdb-sample-nodejs: 3 | image: esdb-sample-nodejs 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | ports: 8 | - "8080:8080" 9 | networks: 10 | - esdb-local-net 11 | depends_on: 12 | esdb-local: 13 | condition: service_started 14 | esdb-local: 15 | image: eventstore/eventstore:latest 16 | environment: 17 | EVENTSTORE_INSECURE: true 18 | ports: 19 | - "2113:2113" 20 | networks: 21 | - esdb-local-net 22 | networks: 23 | esdb-local-net: -------------------------------------------------------------------------------- /Quickstart/Nodejs/esdb-sample-nodejs/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "esdb-sample-nodejs", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "Event Store", 10 | "license": "ISC", 11 | "dependencies": { 12 | "@eventstore/db-client": "^6.1.0", 13 | "express": "^4.18.2" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Quickstart/Python/esdb-sample-python/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.git 3 | **/.gitignore 4 | **/docker-compose* 5 | **/Dockerfile* 6 | LICENSE 7 | README.md -------------------------------------------------------------------------------- /Quickstart/Python/esdb-sample-python/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-slim 2 | WORKDIR /app 3 | COPY . . 4 | RUN pip install --no-cache-dir -r requirements.txt 5 | EXPOSE 8080 6 | CMD ["python", "main.py"] -------------------------------------------------------------------------------- /Quickstart/Python/esdb-sample-python/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | esdb-sample-python: 3 | image: esdb-sample-python 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | ports: 8 | - "8080:8080" 9 | networks: 10 | - esdb-local-net 11 | depends_on: 12 | esdb-local: 13 | condition: service_started 14 | esdb-local: 15 | image: eventstore/eventstore:latest 16 | environment: 17 | EVENTSTORE_INSECURE: true 18 | ports: 19 | - "2113:2113" 20 | networks: 21 | - esdb-local-net 22 | networks: 23 | esdb-local-net: -------------------------------------------------------------------------------- /Quickstart/Python/esdb-sample-python/main.py: -------------------------------------------------------------------------------- 1 | import json 2 | from waitress import serve 3 | from flask import Flask, request 4 | from dataclasses import dataclass 5 | from esdbclient import EventStoreDBClient, NewEvent, StreamState 6 | 7 | app = Flask(__name__) 8 | 9 | event_store = EventStoreDBClient( 10 | uri='esdb://admin:changeit@esdb-local:2113?tls=false&tlsVerifyCert=false' 11 | ) 12 | 13 | visitors_stream = 'visitors-stream' 14 | 15 | @app.route('/hello-world') 16 | def hello_world(): 17 | @dataclass 18 | class VisitorGreeted: 19 | visitor: str 20 | 21 | visitor = request.args.get('visitor', 'Visitor') 22 | visitor_greeted = VisitorGreeted(visitor=visitor) 23 | 24 | event_data = NewEvent( 25 | type='VisitorGreeted', 26 | data=json.dumps(visitor_greeted.__dict__).encode('utf-8') 27 | ) 28 | 29 | append_result = event_store.append_to_stream( 30 | stream_name=visitors_stream, 31 | current_version=StreamState.ANY, 32 | events=[event_data], 33 | ) 34 | 35 | event_stream = event_store.get_stream( 36 | stream_name=visitors_stream, 37 | stream_position=0, 38 | ) 39 | 40 | visitors_greeted = [] 41 | for event in event_stream: 42 | visitors_greeted.append(VisitorGreeted(**json.loads(event.data)).visitor) 43 | 44 | return f"{len(visitors_greeted)} visitors have been greeted, they are: [{','.join(visitors_greeted)}]" 45 | 46 | serve(app, host='0.0.0.0', port=8080) -------------------------------------------------------------------------------- /Quickstart/Python/esdb-sample-python/requirements.txt: -------------------------------------------------------------------------------- 1 | blinker==1.7.0 2 | click==8.1.7 3 | colorama==0.4.6 4 | coverage==7.4.0 5 | dnspython==2.4.2 6 | esdbclient==1.0.16 7 | Flask==3.0.0 8 | grpcio==1.60.0 9 | itsdangerous==2.1.2 10 | Jinja2==3.1.3 11 | MarkupSafe==2.1.3 12 | protobuf==4.25.2 13 | typing_extensions==4.9.0 14 | waitress==2.1.2 15 | Werkzeug==3.0.1 16 | -------------------------------------------------------------------------------- /Quickstart/Rust/esdb-sample-rust/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.dockerignore 2 | **/.git 3 | **/.gitignore 4 | **/docker-compose* 5 | **/Dockerfile* 6 | **/target* 7 | LICENSE 8 | README.md -------------------------------------------------------------------------------- /Quickstart/Rust/esdb-sample-rust/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "esdb-sample-rust" 3 | version = "1.0.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | eventstore = "2.3.0" 8 | rocket = "0.5.0" 9 | serde = "1.0.195" 10 | uuid = { version = "1.7.0" } 11 | -------------------------------------------------------------------------------- /Quickstart/Rust/esdb-sample-rust/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM rust:1.75 2 | WORKDIR /usr/src/esdb-sample-rust 3 | COPY . . 4 | RUN cargo install --path . 5 | EXPOSE 8080 6 | ENTRYPOINT ["esdb-sample-rust"] -------------------------------------------------------------------------------- /Quickstart/Rust/esdb-sample-rust/Rocket.toml: -------------------------------------------------------------------------------- 1 | [default] 2 | address = "0.0.0.0" 3 | port = 8080 -------------------------------------------------------------------------------- /Quickstart/Rust/esdb-sample-rust/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | esdb-sample-rust: 3 | image: esdb-sample-rust 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | ports: 8 | - "8080:8080" 9 | networks: 10 | - esdb-local-net 11 | depends_on: 12 | esdb-local: 13 | condition: service_started 14 | esdb-local: 15 | image: eventstore/eventstore:latest 16 | environment: 17 | EVENTSTORE_INSECURE: true 18 | ports: 19 | - "2113:2113" 20 | networks: 21 | - esdb-local-net 22 | networks: 23 | esdb-local-net: --------------------------------------------------------------------------------