From cf893be6ac9de739995a5c9a723be8f73299c0c6 Mon Sep 17 00:00:00 2001 From: Oskar Dudycz Date: Sat, 3 Dec 2022 21:13:48 +0100 Subject: [PATCH] Added README and solution for HotelManagement --- EventSourcing.NetCore.sln | 3 + README.md | 62 ++++++++++------- Sample/HotelManagement/HotelManagement.sln | 77 ++++++++++++++++++++++ Sample/HotelManagement/README.md | 22 +++++++ Sample/HotelManagement/docker-compose.yml | 66 +++++++++++++++++++ 5 files changed, 206 insertions(+), 24 deletions(-) create mode 100644 Sample/HotelManagement/HotelManagement.sln create mode 100644 Sample/HotelManagement/README.md create mode 100644 Sample/HotelManagement/docker-compose.yml diff --git a/EventSourcing.NetCore.sln b/EventSourcing.NetCore.sln index c831694a0..d14f11ea6 100644 --- a/EventSourcing.NetCore.sln +++ b/EventSourcing.NetCore.sln @@ -338,6 +338,9 @@ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Helpdesk.Api.Tests", "Sample\Helpdesk\Helpdesk.Api.Tests\Helpdesk.Api.Tests.csproj", "{525AA67F-1D3A-4752-9A3F-A6A82819CF70}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "HotelManagement", "HotelManagement", "{2D239E0E-D194-4746-AF55-CEF03A65F2F5}" + ProjectSection(SolutionItems) = preProject + Sample\HotelManagement\README.md = Sample\HotelManagement\README.md + EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotelManagement.Tests", "Sample\HotelManagement\HotelManagement.Tests\HotelManagement.Tests.csproj", "{FC2B0AED-92D9-4020-B902-AED8A2CA653D}" EndProject diff --git a/README.md b/README.md index ba00b80aa..4fc30ee12 100644 --- a/README.md +++ b/README.md @@ -18,10 +18,11 @@ Tutorial, practical samples and other resources about Event Sourcing in .NET. Se - [2.2. Practical Introduction to Event Sourcing with EventStoreDB](#22-practical-introduction-to-event-sourcing-with-eventstoredb) - [2.3 Let's build the worst Event Sourcing system!](#23-lets-build-the-worst-event-sourcing-system) - [2.4 The Light and The Dark Side of the Event-Driven Design](#24-the-light-and-the-dark-side-of-the-event-driven-design) - - [2.5 Conversation with Yves Lorphelin about CQRS](#25-conversation-with-yves-lorphelin-about-cqrs) - - [2.6. CQRS is Simpler than you think with C#9 & NET5](#26-cqrs-is-simpler-than-you-think-with-c9--net5) - - [2.7. Never Lose Data Again - Event Sourcing to the Rescue!](#27-never-lose-data-again---event-sourcing-to-the-rescue) - - [2.8. How to deal with privacy and GDPR in Event-Sourced systems](#28-how-to-deal-with-privacy-and-gdpr-in-event-sourced-systems) + - [2.5 Implementing Distributed Processes](#25-implementing-distributed-processes) + - [2.6 Conversation with Yves Lorphelin about CQRS](#26-conversation-with-yves-lorphelin-about-cqrs) + - [2.7. CQRS is Simpler than you think with C#9 & NET5](#27-cqrs-is-simpler-than-you-think-with-c9--net5) + - [2.8. Never Lose Data Again - Event Sourcing to the Rescue!](#28-never-lose-data-again---event-sourcing-to-the-rescue) + - [2.9. How to deal with privacy and GDPR in Event-Sourced systems](#29-how-to-deal-with-privacy-and-gdpr-in-event-sourced-systems) - [3. Support](#3-support) - [4. Prerequisites](#4-prerequisites) - [5. Tools used](#5-tools-used) @@ -29,14 +30,15 @@ Tutorial, practical samples and other resources about Event Sourcing in .NET. Se - [6.1 Pragmatic Event Sourcing With Marten](#61-pragmatic-event-sourcing-with-marten) - [6.2 ECommerce with Marten](#62-ecommerce-with-marten) - [6.3 Simple EventSourcing with EventStoreDB](#63-simple-eventsourcing-with-eventstoredb) - - [6.4 ECommerce with EventStoreDB](#64-ecommerce-with-eventstoredb) - - [6.5 Warehouse](#65-warehouse) - - [6.6 Warehouse Minimal API](#66-warehouse-minimal-api) - - [6.7 Event Versioning](#67-event-versioning) - - [6.8 Event Pipelines](#68-event-pipelines) - - [6.9 Meetings Management with Marten](#69-meetings-management-with-marten) - - [6.10 Cinema Tickets Reservations with Marten](#610-cinema-tickets-reservations-with-marten) - - [6.11 SmartHome IoT with Marten](#611-smarthome-iot-with-marten) + - [6.4 Implementing Distributed Processes](#64-implementing-distributed-processes) + - [6.5 ECommerce with EventStoreDB](#65-ecommerce-with-eventstoredb) + - [6.6 Warehouse](#66-warehouse) + - [6.7 Warehouse Minimal API](#67-warehouse-minimal-api) + - [6.8 Event Versioning](#68-event-versioning) + - [6.9 Event Pipelines](#69-event-pipelines) + - [6.10 Meetings Management with Marten](#610-meetings-management-with-marten) + - [6.11 Cinema Tickets Reservations with Marten](#611-cinema-tickets-reservations-with-marten) + - [6.12 SmartHome IoT with Marten](#612-smarthome-iot-with-marten) - [7. Self-paced training Kits](#7-self-paced-training-kits) - [7.1 Introduction to Event Sourcing](#71-introduction-to-event-sourcing) - [7.2 Build your own Event Store](#72-build-your-own-event-store) @@ -570,19 +572,23 @@ Read more in the article: The Light and The Dark Side of the Event-Driven Design -### 2.5 Conversation with [Yves Lorphelin](https://github.com/ylorph/) about CQRS +### 2.5 Implementing Distributed Processes + +Implementing Distributed Processes + +### 2.6 Conversation with [Yves Lorphelin](https://github.com/ylorph/) about CQRS Event Store Conversations: Yves Lorphelin talks to Oskar Dudycz about CQRS (EN) -### 2.6. CQRS is Simpler than you think with C#9 & NET5 +### 2.7. CQRS is Simpler than you think with C#9 & NET5 CQRS is Simpler than you think with C#9 & NET5 -### 2.7. Never Lose Data Again - Event Sourcing to the Rescue! +### 2.8. Never Lose Data Again - Event Sourcing to the Rescue! Never Lose Data Again - Event Sourcing to the Rescue! -### 2.8. How to deal with privacy and GDPR in Event-Sourced systems +### 2.9. How to deal with privacy and GDPR in Event-Sourced systems How to deal with privacy and GDPR in Event-Sourced systems @@ -646,26 +652,34 @@ Samples are using CQRS architecture. They're sliced based on the business module - Builds read models using [Subscription to `$all`](https://developers.eventstore.com/clients/grpc/subscribing-to-streams/#subscribing-to-all), - Read models are stored as Postgres tables using EntityFramework. -### 6.4 [ECommerce with EventStoreDB](./Sample/EventStoreDB/ECommerce) +### 6.4 [Implementing Distributed Processes](./Sample/HotelManagement) +- orchestrate and coordinate business workflow spanning across multiple aggregates using [Saga pattern](https://event-driven.io/en/saga_process_manager_distributed_transactions/), +- handle distributed processing both for asynchronous commands scheduling and events publishing, +- getting at-least-once delivery guarantee, +- implementing command store and outbox pattern on top of Marten and EventStoreDB, +- unit testing aggregates and Saga with a little help from [Ogooreck](https://github.com/oskardudycz/Ogooreck), +- testing asynchronous code. + +### 6.5 [ECommerce with EventStoreDB](./Sample/EventStoreDB/ECommerce) - typical Event Sourcing and CQRS flow, - DDD using Aggregates, - stores events to EventStoreDB, - Builds read models using [Subscription to `$all`](https://developers.eventstore.com/clients/grpc/subscribing-to-streams/#subscribing-to-all). - Read models are stored as Marten documents. -### 6.5 [Warehouse](./Sample/Warehouse) +### 6.6 [Warehouse](./Sample/Warehouse) - simplest CQRS flow using .NET Endpoints, - example of how and where to use C# Records, Nullable Reference Types, etc, - No Event Sourcing! Using Entity Framework to show that CQRS is not bounded to Event Sourcing or any type of storage, - No Aggregates! CQRS do not need DDD. Business logic can be handled in handlers. -### 6.6 [Warehouse Minimal API](./Sample/Warehouse.MinimalAPI/) +### 6.7 [Warehouse Minimal API](./Sample/Warehouse.MinimalAPI/) Variation of the previous example, but: - using Minimal API, - example how to inject handlers in MediatR like style to decouple API from handlers. - 📝 Read more [CQRS is simpler than you think with .NET 6 and C# 10](https://event-driven.io/en/cqrs_is_simpler_than_you_think_with_net6/?utm_source=event_sourcing_net) -### 6.7 [Event Versioning](./Sample/EventsVersioning) +### 6.8 [Event Versioning](./Sample/EventsVersioning) Shows how to handle basic event schema versioning scenarios using event and stream transformations (e.g. upcasting): - [Simple mapping](./Sample/EventsVersioning/#simple-mapping) - [New not required property](./Sample/EventsVersioning/#new-not-required-property) @@ -680,7 +694,7 @@ Shows how to handle basic event schema versioning scenarios using event and stre - [Summary](./Sample/EventsVersioning/#summary) - 📝 [Simple patterns for events schema versioning](https://event-driven.io/en/simple_events_versioning_patterns/?utm_source=event_sourcing_net) -### 6.8 [Event Pipelines](./Sample/EventPipelines) +### 6.9 [Event Pipelines](./Sample/EventPipelines) Shows how to compose event handlers in the processing pipelines to: - filter events, - transform them, @@ -692,7 +706,7 @@ Shows how to compose event handlers in the processing pipelines to: - integrates with MediatR if you want to. - 📝 Read more [How to build a simple event pipeline](https://event-driven.io/en/how_to_build_simple_event_pipeline/?utm_source=event_sourcing_net) -### 6.9 [Meetings Management with Marten](./Sample/MeetingsManagement/) +### 6.10 [Meetings Management with Marten](./Sample/MeetingsManagement/) - typical Event Sourcing and CQRS flow, - DDD using Aggregates, - microservices example, @@ -700,12 +714,12 @@ Shows how to compose event handlers in the processing pipelines to: - Kafka as a messaging platform to integrate microservices, - read models handled in separate microservice and stored to other database (ElasticSearch) -### 6.10 [Cinema Tickets Reservations with Marten](./Sample/Tickets/) +### 6.11 [Cinema Tickets Reservations with Marten](./Sample/Tickets/) - typical Event Sourcing and CQRS flow, - DDD using Aggregates, - stores events to Marten. -### 6.11 [SmartHome IoT with Marten](./Sample/AsyncProjections/) +### 6.12 [SmartHome IoT with Marten](./Sample/AsyncProjections/) - typical Event Sourcing and CQRS flow, - DDD using Aggregates, - stores events to Marten, diff --git a/Sample/HotelManagement/HotelManagement.sln b/Sample/HotelManagement/HotelManagement.sln new file mode 100644 index 000000000..4f8a201fd --- /dev/null +++ b/Sample/HotelManagement/HotelManagement.sln @@ -0,0 +1,77 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{1F491B11-2201-4616-976F-A3012D95BD9C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.WebApi", "..\..\Core.WebApi\Core.WebApi.csproj", "{AE9175ED-054C-46F6-A91D-0F2691BD935B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Docs", "Docs", "{6C654A81-F8CF-46DE-9CD8-442162F3F6FD}" +ProjectSection(SolutionItems) = preProject + README.md = README.md +EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docker", "docker", "{88584BD6-A70F-464D-9EFD-609C5E1C10DE}" + ProjectSection(SolutionItems) = preProject + docker-compose.yml = docker-compose.yml + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core", "..\..\Core\Core.csproj", "{39647310-1DEE-4464-94CC-59CBC2A2B185}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Serialization", "..\..\Core.Serialization\Core.Serialization.csproj", "{E65E4940-C29F-4558-AB21-8AC2F54528DE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Testing", "..\..\Core.Testing\Core.Testing.csproj", "{A1894305-B86B-411D-ABCE-115CF5142D92}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotelManagement", "HotelManagement\HotelManagement.csproj", "{13273AB6-CD0F-46F5-9760-DD5C2938DBCF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotelManagement.Api", "HotelManagement.Api\HotelManagement.Api.csproj", "{2196B997-E94E-49D7-851D-2CE5223839C7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotelManagement.Tests", "HotelManagement.Tests\HotelManagement.Tests.csproj", "{A05744BA-0A7C-40CD-B7C7-A75945278989}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Core.Marten", "..\..\Core.Marten\Core.Marten.csproj", "{425F1AD6-E58D-4EDE-813E-EE42FF08ADBA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {AE9175ED-054C-46F6-A91D-0F2691BD935B} = {1F491B11-2201-4616-976F-A3012D95BD9C} + {39647310-1DEE-4464-94CC-59CBC2A2B185} = {1F491B11-2201-4616-976F-A3012D95BD9C} + {E65E4940-C29F-4558-AB21-8AC2F54528DE} = {1F491B11-2201-4616-976F-A3012D95BD9C} + {A1894305-B86B-411D-ABCE-115CF5142D92} = {1F491B11-2201-4616-976F-A3012D95BD9C} + {425F1AD6-E58D-4EDE-813E-EE42FF08ADBA} = {1F491B11-2201-4616-976F-A3012D95BD9C} + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {AE9175ED-054C-46F6-A91D-0F2691BD935B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AE9175ED-054C-46F6-A91D-0F2691BD935B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AE9175ED-054C-46F6-A91D-0F2691BD935B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AE9175ED-054C-46F6-A91D-0F2691BD935B}.Release|Any CPU.Build.0 = Release|Any CPU + {39647310-1DEE-4464-94CC-59CBC2A2B185}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39647310-1DEE-4464-94CC-59CBC2A2B185}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39647310-1DEE-4464-94CC-59CBC2A2B185}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39647310-1DEE-4464-94CC-59CBC2A2B185}.Release|Any CPU.Build.0 = Release|Any CPU + {E65E4940-C29F-4558-AB21-8AC2F54528DE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E65E4940-C29F-4558-AB21-8AC2F54528DE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E65E4940-C29F-4558-AB21-8AC2F54528DE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E65E4940-C29F-4558-AB21-8AC2F54528DE}.Release|Any CPU.Build.0 = Release|Any CPU + {A1894305-B86B-411D-ABCE-115CF5142D92}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A1894305-B86B-411D-ABCE-115CF5142D92}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A1894305-B86B-411D-ABCE-115CF5142D92}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A1894305-B86B-411D-ABCE-115CF5142D92}.Release|Any CPU.Build.0 = Release|Any CPU + {13273AB6-CD0F-46F5-9760-DD5C2938DBCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {13273AB6-CD0F-46F5-9760-DD5C2938DBCF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {13273AB6-CD0F-46F5-9760-DD5C2938DBCF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {13273AB6-CD0F-46F5-9760-DD5C2938DBCF}.Release|Any CPU.Build.0 = Release|Any CPU + {2196B997-E94E-49D7-851D-2CE5223839C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2196B997-E94E-49D7-851D-2CE5223839C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2196B997-E94E-49D7-851D-2CE5223839C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2196B997-E94E-49D7-851D-2CE5223839C7}.Release|Any CPU.Build.0 = Release|Any CPU + {A05744BA-0A7C-40CD-B7C7-A75945278989}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A05744BA-0A7C-40CD-B7C7-A75945278989}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A05744BA-0A7C-40CD-B7C7-A75945278989}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A05744BA-0A7C-40CD-B7C7-A75945278989}.Release|Any CPU.Build.0 = Release|Any CPU + {425F1AD6-E58D-4EDE-813E-EE42FF08ADBA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {425F1AD6-E58D-4EDE-813E-EE42FF08ADBA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {425F1AD6-E58D-4EDE-813E-EE42FF08ADBA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {425F1AD6-E58D-4EDE-813E-EE42FF08ADBA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Sample/HotelManagement/README.md b/Sample/HotelManagement/README.md new file mode 100644 index 000000000..039186b95 --- /dev/null +++ b/Sample/HotelManagement/README.md @@ -0,0 +1,22 @@ +[![Twitter Follow](https://img.shields.io/twitter/follow/oskar_at_net?style=social)](https://twitter.com/oskar_at_net) [![Github Sponsors](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&link=https://github.com/sponsors/oskardudycz/)](https://github.com/sponsors/oskardudycz/) [![blog](https://img.shields.io/badge/blog-event--driven.io-brightgreen)](https://event-driven.io/?utm_source=event_sourcing_jvm) [![blog](https://img.shields.io/badge/%F0%9F%9A%80-Architecture%20Weekly-important)](https://www.architecture-weekly.com/?utm_source=event_sourcing_net) + +# Implementing Distributed Processes + +Added example of distributed processes management using Group Guests Checkout as an example. + +It was modelled and explained in detail in the [Implementing Distributed Processes Webinar](https://www.architecture-weekly.com/p/webinar-3-implementing-distributed): + +How to deal with privacy and GDPR in Event-Sourced systems + +It shows how to: +- orchestrate and coordinate business workflow spanning across multiple aggregates using [Saga pattern](https://event-driven.io/en/saga_process_manager_distributed_transactions/), +- handle distributed processing both for asynchronous commands scheduling and events publishing, +- getting at-least-once delivery guarantee, +- implementing command store and outbox pattern on top of Marten and EventStoreDB, +- unit testing aggregates and Saga with a little help from [Ogooreck](https://github.com/oskardudycz/Ogooreck), +- testing asynchronous code. + +Read more in: +- [Saga and Process Manager - distributed processes in practice](https://event-driven.io/en/saga_process_manager_distributed_transactions/) +- [Event-driven distributed processes by example](https://event-driven.io/en/saga_process_manager_distributed_transactions/). + diff --git a/Sample/HotelManagement/docker-compose.yml b/Sample/HotelManagement/docker-compose.yml new file mode 100644 index 000000000..4af158dfa --- /dev/null +++ b/Sample/HotelManagement/docker-compose.yml @@ -0,0 +1,66 @@ +version: "3" +services: + postgres: + image: clkao/postgres-plv8 + container_name: postgres + environment: + POSTGRES_PASSWORD: Password12! + ports: + - '5432:5432' + networks: + - postgres + + pgadmin: + image: dpage/pgadmin4 + container_name: pgadmin_container + environment: + PGADMIN_DEFAULT_EMAIL: ${PGADMIN_DEFAULT_EMAIL:-pgadmin4@pgadmin.org} + PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_DEFAULT_PASSWORD:-admin} + PGADMIN_CONFIG_SERVER_MODE: 'False' + volumes: + - pgadmin:/var/lib/pgadmin + ports: + - "${PGADMIN_PORT:-5050}:80" + networks: + - postgres + + ####################################################### + # EventStoreDB + ####################################################### + eventstore.db: + image: eventstore/eventstore:21.10.1-buster-slim + # use this image if you're running ARM-based proc like Apple M1 + # image: ghcr.io/eventstore/eventstore:21.10.0-alpha-arm64v8 + environment: + - EVENTSTORE_CLUSTER_SIZE=1 + - EVENTSTORE_RUN_PROJECTIONS=All + - EVENTSTORE_START_STANDARD_PROJECTIONS=true + - EVENTSTORE_EXT_TCP_PORT=1113 + - EVENTSTORE_HTTP_PORT=2113 + - EVENTSTORE_INSECURE=true + - EVENTSTORE_ENABLE_EXTERNAL_TCP=true + - EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true + ports: + - '1113:1113' + - '2113:2113' + volumes: + - type: volume + source: eventstore-volume-data + target: /var/lib/eventstore + - type: volume + source: eventstore-volume-logs + target: /var/log/eventstore + networks: + - eventstore.db + +networks: + postgres: + driver: bridge + eventstore.db: + driver: bridge + +volumes: + postgres: + pgadmin: + eventstore-volume-data: + eventstore-volume-logs: