├── readme-images ├── 1_no_cqrs.png ├── 5_event_sourcing.png ├── 2_separe_commands_queries.png ├── 4_separate_storage_engines.png └── 3_separate_models_commands_queries.png ├── NoCqrs ├── appsettings.json ├── appsettings.Development.json ├── Domain │ ├── IDataStore.cs │ ├── IOfferRepository.cs │ ├── IProductRepository.cs │ ├── SysTime.cs │ ├── CoversList.cs │ ├── UnitPrice.cs │ ├── Cover.cs │ ├── Person.cs │ ├── Car.cs │ ├── Product.cs │ ├── ValueObject.cs │ ├── PolicyVersions.cs │ ├── PolicyCover.cs │ ├── IPolicyRepository.cs │ └── ValidityPeriod.cs ├── Utils │ └── StringExtensions.cs ├── Services │ ├── CancelLastAnnexRequest.cs │ ├── ConfirmTerminationRequest.cs │ ├── CreatePolicyRequest.cs │ ├── ConfirmBuyAdditionalCoverRequest.cs │ ├── TerminatePolicyRequest.cs │ ├── SearchPolicyRequest.cs │ ├── PolicyInfoDto.cs │ ├── BuyAdditionalCoverRequest.cs │ ├── PolicyInfoDtoAssembler.cs │ ├── PolicyDto.cs │ └── PolicyDtoAssembler.cs ├── Init │ ├── Cars.cs │ ├── Persons.cs │ ├── Products.cs │ ├── DbInitializer.cs │ └── Offers.cs ├── NoCqrs.csproj ├── Program.cs ├── Installers │ └── EFInstaller.cs ├── Properties │ └── launchSettings.json ├── DataAccess │ ├── EFProductRepository.cs │ ├── EFDataStore.cs │ ├── EFOfferRepository.cs │ ├── InsuranceDbContext.cs │ └── EFPolicyRepository.cs ├── Startup.cs └── Controllers │ └── PoliciesController.cs ├── CqrsWithEs ├── Domain │ ├── Policy │ │ ├── PolicyStatus.cs │ │ ├── PolicyVersionStatus.cs │ │ ├── Events │ │ │ ├── CoverageExtendedPolicyVersionCancelled.cs │ │ │ ├── TerminalPolicyVersionConfirmed.cs │ │ │ ├── CoverageExtendedPolicyVersionConfirmed.cs │ │ │ ├── PolicyEventsData.cs │ │ │ ├── CoverageExtendedPolicyVersionCreated.cs │ │ │ ├── TerminalPolicyVersionCreated.cs │ │ │ └── InitialPolicyVersionCreated.cs │ │ ├── IPolicyRepository.cs │ │ ├── UnitPrice.cs │ │ ├── IPolicyState.cs │ │ ├── PolicyVersions.cs │ │ ├── PolicyCover.cs │ │ └── PolicyVersion.cs │ ├── Offer │ │ └── IOfferRepository.cs │ ├── Product │ │ ├── IProductRepository.cs │ │ ├── Cover.cs │ │ └── Product.cs │ ├── Common │ │ ├── Person.cs │ │ ├── Car.cs │ │ └── ValidityPeriod.cs │ └── Base │ │ ├── ValueObject.cs │ │ └── AggregateRoot.cs ├── appsettings.json ├── appsettings.Development.json ├── ReadModel │ ├── ReadModelInstaller.cs │ ├── PolicyInfo.cs │ ├── PolicyVersionDto.cs │ └── PolicyInfoDao.cs ├── Commands │ ├── ConfirmTerminationCommand.cs │ ├── TerminatePolicyCommand.cs │ ├── ConfirmBuyAdditionalCoverCommand.cs │ ├── CreatePolicyCommand.cs │ ├── BuyAdditionalCoverCommand.cs │ ├── ConfirmTerminationHandler.cs │ ├── ConfirmBuyAdditionalCoverHandler.cs │ ├── TerminatePolicyHandler.cs │ ├── CreatePolicyHandler.cs │ └── BuyAdditionalCoverHandler.cs ├── Program.cs ├── DataAccess │ ├── DataAccessInstaller.cs │ ├── InMemoryPolicyRepository.cs │ ├── InMemoryProductsRepository.cs │ └── InMemoryOfferRepository.cs ├── Init │ ├── DataLoader.cs │ ├── DataLoaderInstaller.cs │ └── SampleOfferData.cs ├── CqrsWithEs.csproj ├── Properties │ └── launchSettings.json ├── Scripts │ └── create_readmodel_tables.sql ├── EventHandlers │ └── PolicyInfoSynchronizer.cs ├── Controllers │ └── PoliciesController.cs └── Startup.cs ├── SeparateModels ├── appsettings.Development.json ├── Utils │ └── StringExtensions.cs ├── Domain │ ├── SysTime.cs │ ├── IPolicyRepository.cs │ ├── IDataStore.cs │ ├── IOfferRepository.cs │ ├── IProductRepository.cs │ ├── CoversList.cs │ ├── UnitPrice.cs │ ├── Cover.cs │ ├── Person.cs │ ├── Car.cs │ ├── Product.cs │ ├── ValueObject.cs │ ├── PolicyVersions.cs │ ├── PolicyCover.cs │ ├── ValidityPeriod.cs │ └── PolicyEvents.cs ├── appsettings.json ├── Init │ ├── Cars.cs │ ├── Persons.cs │ ├── Products.cs │ ├── Offers.cs │ └── DbInitializer.cs ├── Queries │ ├── GetPolicyVersionDetailsQuery.cs │ ├── GetPolicyVersionsListQuery.cs │ ├── FindPoliciesQuery.cs │ ├── GetPolicyVersionDetailsHandler.cs │ ├── GetPolicyVersionsListHandler.cs │ └── FindPoliciesHandler.cs ├── Commands │ ├── CancelLastAnnexCommand.cs │ ├── CreatePolicyCommand.cs │ ├── ConfirmTerminationCommand.cs │ ├── TerminatePolicyCommand.cs │ ├── ConfirmBuyAdditionalCoverCommand.cs │ ├── BuyAdditionalCoverCommand.cs │ ├── TerminatePolicyHandler.cs │ ├── CreatePolicyHandler.cs │ ├── ConfirmTerminationHandler.cs │ ├── CancelLastAnnexHandler.cs │ ├── ConfirmBuyAdditionalCoverHandler.cs │ └── BuyAdditionalCoverHandler.cs ├── Program.cs ├── ReadModels │ ├── PolicyInfoDto.cs │ ├── PolicyVersionsListDto.cs │ ├── PolicyFilter.cs │ ├── ReadModelsInstaller.cs │ ├── PolicyVersionDto.cs │ ├── PolicyInfoDtoFinder.cs │ └── PolicyInfoDtoProjection.cs ├── Installers │ └── EFInstaller.cs ├── Properties │ └── launchSettings.json ├── SeparateModels.csproj ├── DataAccess │ └── Marten │ │ ├── ProtectedSettersContractResolver.cs │ │ └── MartenInstaller.cs ├── EventHandlers │ ├── PolicyAnnexedProjectionsHandler.cs │ ├── PolicyTerminatedProjectionsHandler.cs │ ├── PolicyChangesCancelledProjectionsHandler.cs │ └── PolicyCreatedProjectionsHandler.cs ├── DbScripts │ └── create_readmodel_tables.sql ├── Startup.cs └── Controllers │ └── PoliciesController.cs ├── NoCqrs.Tests ├── TestData │ ├── CarsTestData.cs │ ├── PersonsTestData.cs │ ├── ProductsTestData.cs │ ├── PolicyTestData.cs │ └── OffersTestData.cs ├── NoCqrs.Tests.csproj ├── PolicyCancelAnnexUnitTests.cs ├── PolicyTerminationUnitTests.cs ├── PolicyAnnexesUnitTest.cs └── PolicyCreationUnitTest.cs ├── CqrsWithEs.Tests ├── TestData │ ├── CarsTestData.cs │ ├── PersonsTestData.cs │ ├── ProductsTestData.cs │ ├── OffersTestData.cs │ └── PolicyTestData.cs ├── CqrsWithEs.Tests.csproj ├── Asserts │ ├── PolicyEventsStreamAsserts.cs │ └── PolicyAsserts.cs ├── PolicyTerminationUnitTests.cs ├── BuyAdditionalCoverHandlerTest.cs ├── HttpClientAsserts │ └── HttpClientExtensions.cs └── PolicyAnnexUnitTests.cs ├── README.md └── DotNetCqrsIntro.sln /readme-images/1_no_cqrs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asc-lab/dotnet-cqrs-intro/HEAD/readme-images/1_no_cqrs.png -------------------------------------------------------------------------------- /readme-images/5_event_sourcing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asc-lab/dotnet-cqrs-intro/HEAD/readme-images/5_event_sourcing.png -------------------------------------------------------------------------------- /readme-images/2_separe_commands_queries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asc-lab/dotnet-cqrs-intro/HEAD/readme-images/2_separe_commands_queries.png -------------------------------------------------------------------------------- /NoCqrs/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /readme-images/4_separate_storage_engines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asc-lab/dotnet-cqrs-intro/HEAD/readme-images/4_separate_storage_engines.png -------------------------------------------------------------------------------- /readme-images/3_separate_models_commands_queries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/asc-lab/dotnet-cqrs-intro/HEAD/readme-images/3_separate_models_commands_queries.png -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Policy/PolicyStatus.cs: -------------------------------------------------------------------------------- 1 | namespace CqrsWithEs.Domain.Policy 2 | { 3 | public enum PolicyStatus 4 | { 5 | Active, 6 | Terminated 7 | } 8 | } -------------------------------------------------------------------------------- /CqrsWithEs/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*", 8 | "ConnectionStrings": { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Policy/PolicyVersionStatus.cs: -------------------------------------------------------------------------------- 1 | namespace CqrsWithEs.Domain.Policy 2 | { 3 | public enum PolicyVersionStatus 4 | { 5 | Draft, 6 | Active, 7 | Cancelled 8 | } 9 | } -------------------------------------------------------------------------------- /NoCqrs/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CqrsWithEs/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /SeparateModels/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Debug", 5 | "System": "Information", 6 | "Microsoft": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Policy/Events/CoverageExtendedPolicyVersionCancelled.cs: -------------------------------------------------------------------------------- 1 | using CqrsWithEs.Domain.Base; 2 | 3 | namespace CqrsWithEs.Domain.Policy.Events 4 | { 5 | public class CoverageExtendedPolicyVersionCancelled : Event 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Policy/IPolicyRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CqrsWithEs.Domain.Policy 4 | { 5 | public interface IPolicyRepository 6 | { 7 | Policy GetById(Guid Id); 8 | void Save(Policy policy, int expectedVersion); 9 | } 10 | } -------------------------------------------------------------------------------- /SeparateModels/Utils/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | namespace SeparateModels.Utils 2 | { 3 | public static class StringExtensions 4 | { 5 | public static bool NotBlank(this string text) 6 | { 7 | return !string.IsNullOrWhiteSpace(text); 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /NoCqrs/Domain/IDataStore.cs: -------------------------------------------------------------------------------- 1 | namespace NoCqrs.Domain 2 | { 3 | public interface IDataStore 4 | { 5 | IProductRepository Products { get; } 6 | IOfferRepository Offers { get; } 7 | IPolicyRepository Policies { get; } 8 | void CommitChanges(); 9 | } 10 | } -------------------------------------------------------------------------------- /NoCqrs/Domain/IOfferRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace NoCqrs.Domain 4 | { 5 | public interface IOfferRepository 6 | { 7 | Offer WithNumber(string number); 8 | 9 | List All(); 10 | 11 | void Add(Offer offer); 12 | } 13 | } -------------------------------------------------------------------------------- /NoCqrs/Utils/StringExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NoCqrs.Utils 4 | { 5 | public static class StringExtensions 6 | { 7 | public static bool NotBlank(this string text) 8 | { 9 | return !string.IsNullOrWhiteSpace(text); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /NoCqrs/Domain/IProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace NoCqrs.Domain 4 | { 5 | public interface IProductRepository 6 | { 7 | void Add(Product product); 8 | 9 | Product WithCode(string code); 10 | 11 | List All(); 12 | } 13 | } -------------------------------------------------------------------------------- /SeparateModels/Domain/SysTime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SeparateModels.Domain 4 | { 5 | public class SysTime 6 | { 7 | public static Func CurrentTimeProvider { get; set; } = () => DateTime.Now; 8 | public static DateTime CurrentTime => CurrentTimeProvider(); 9 | } 10 | } -------------------------------------------------------------------------------- /NoCqrs/Domain/SysTime.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NodaTime; 3 | 4 | namespace NoCqrs.Domain 5 | { 6 | public class SysTime 7 | { 8 | public static Func CurrentTimeProvider { get; set; } = () => DateTime.Now; 9 | public static DateTime CurrentTime => CurrentTimeProvider(); 10 | } 11 | } -------------------------------------------------------------------------------- /SeparateModels/Domain/IPolicyRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace SeparateModels.Domain 5 | { 6 | public interface IPolicyRepository 7 | { 8 | Task WithNumber(string number); 9 | 10 | void Add(Policy policy); 11 | } 12 | } -------------------------------------------------------------------------------- /SeparateModels/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*", 8 | "ConnectionStrings": { 9 | "DefaultConnection": "User ID=lab_user;Password=lab_pass;Database=lab_cqrs_dotnet_demo;Host=localhost;Port=5432" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /SeparateModels/Domain/IDataStore.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace SeparateModels.Domain 4 | { 5 | public interface IDataStore 6 | { 7 | IProductRepository Products { get; } 8 | IOfferRepository Offers { get; } 9 | IPolicyRepository Policies { get; } 10 | Task CommitChanges(); 11 | } 12 | } -------------------------------------------------------------------------------- /SeparateModels/Domain/IOfferRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace SeparateModels.Domain 5 | { 6 | public interface IOfferRepository 7 | { 8 | Task WithNumber(string number); 9 | 10 | Task> All(); 11 | 12 | void Add(Offer offer); 13 | } 14 | } -------------------------------------------------------------------------------- /SeparateModels/Domain/IProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | 4 | namespace SeparateModels.Domain 5 | { 6 | public interface IProductRepository 7 | { 8 | void Add(Product product); 9 | 10 | Task WithCode(string code); 11 | 12 | Task> All(); 13 | } 14 | } -------------------------------------------------------------------------------- /NoCqrs/Services/CancelLastAnnexRequest.cs: -------------------------------------------------------------------------------- 1 | namespace NoCqrs.Services 2 | { 3 | public class CancelLastAnnexRequest 4 | { 5 | public string PolicyNumber { get; set; } 6 | } 7 | 8 | public class CancelLastAnnexResult 9 | { 10 | public string PolicyNumber { get; set; } 11 | public int LastActiveVersionNumber { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /NoCqrs/Init/Cars.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NoCqrs.Domain; 3 | 4 | namespace NoCqrs.Init 5 | { 6 | public class Cars 7 | { 8 | public static Car OldFordFocus() 9 | { 10 | return new Car 11 | ( 12 | "Ford Focus", 13 | "WAW1010", 14 | 2005 15 | ); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /SeparateModels/Domain/CoversList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace SeparateModels.Domain 5 | { 6 | public static class CoversList 7 | { 8 | public static Cover WithCode(this IEnumerable covers, string code) 9 | { 10 | return covers?.FirstOrDefault(c => c.Code == code); 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /SeparateModels/Init/Cars.cs: -------------------------------------------------------------------------------- 1 | using SeparateModels.Domain; 2 | 3 | namespace SeparateModels.Init 4 | { 5 | public class Cars 6 | { 7 | public static Car OldFordFocus() 8 | { 9 | return new Car 10 | ( 11 | "Ford Focus", 12 | "WAW1010", 13 | 2005 14 | ); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /NoCqrs/Init/Persons.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NoCqrs.Domain; 3 | 4 | namespace NoCqrs.Init 5 | { 6 | public class Persons 7 | { 8 | public static Person Kowalski() 9 | { 10 | return new Person 11 | ( 12 | "Jan", 13 | "Kowalski", 14 | "1111111116" 15 | ); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /NoCqrs/Domain/CoversList.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace NoCqrs.Domain 6 | { 7 | public static class CoversList 8 | { 9 | public static Cover WithCode(this IEnumerable covers, string code) 10 | { 11 | return covers?.FirstOrDefault(c => c.Code == code); 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /SeparateModels/Init/Persons.cs: -------------------------------------------------------------------------------- 1 | using SeparateModels.Domain; 2 | 3 | namespace SeparateModels.Init 4 | { 5 | public class Persons 6 | { 7 | public static Person Kowalski() 8 | { 9 | return new Person 10 | ( 11 | "Jan", 12 | "Kowalski", 13 | "1111111116" 14 | ); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /SeparateModels/Queries/GetPolicyVersionDetailsQuery.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using SeparateModels.ReadModels; 3 | using SeparateModels.Services; 4 | 5 | namespace SeparateModels.Queries 6 | { 7 | public class GetPolicyVersionDetailsQuery : IRequest 8 | { 9 | public string PolicyNumber { get; set; } 10 | public int VersionNumber { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /NoCqrs.Tests/TestData/CarsTestData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NoCqrs.Domain; 3 | 4 | namespace NoCqrs.Tests 5 | { 6 | public class CarsTestData 7 | { 8 | public static Car OldFordFocus() 9 | { 10 | return new Car 11 | ( 12 | "Ford Focus", 13 | "WAW1010", 14 | 2005 15 | ); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Offer/IOfferRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.Threading.Tasks; 4 | 5 | namespace CqrsWithEs.Domain.Offer 6 | { 7 | public interface IOfferRepository 8 | { 9 | Task WithNumber(string number); 10 | 11 | Task> All(); 12 | 13 | void Add(Offer offer); 14 | } 15 | } -------------------------------------------------------------------------------- /NoCqrs.Tests/TestData/PersonsTestData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NoCqrs.Domain; 3 | 4 | namespace NoCqrs.Tests 5 | { 6 | public class PersonsTestData 7 | { 8 | public static Person Kowalski() 9 | { 10 | return new Person 11 | ( 12 | "Jan", 13 | "Kowalski", 14 | "1111111116" 15 | ); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /NoCqrs/Services/ConfirmTerminationRequest.cs: -------------------------------------------------------------------------------- 1 | namespace NoCqrs.Services 2 | { 3 | public class ConfirmTerminationRequest 4 | { 5 | public string PolicyNumber { get; set; } 6 | public int VersionToConfirmNumber { get; set; } 7 | } 8 | 9 | public class ConfirmTerminationResult 10 | { 11 | public string PolicyNumber { get; set; } 12 | public int VersionConfirmed { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /NoCqrs/Services/CreatePolicyRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NoCqrs.Services 4 | { 5 | public class CreatePolicyRequest 6 | { 7 | public string OfferNumber { get; set; } 8 | public DateTime PurchaseDate { get; set; } 9 | public DateTime PolicyStartDate { get; set; } 10 | } 11 | 12 | public class CreatePolicyResult 13 | { 14 | public string PolicyNumber { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /SeparateModels/Commands/CancelLastAnnexCommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace SeparateModels.Services 4 | { 5 | public class CancelLastAnnexCommand : IRequest 6 | { 7 | public string PolicyNumber { get; set; } 8 | } 9 | 10 | public class CancelLastAnnexResult 11 | { 12 | public string PolicyNumber { get; set; } 13 | public int LastActiveVersionNumber { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /CqrsWithEs.Tests/TestData/CarsTestData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CqrsWithEs.Domain; 3 | using CqrsWithEs.Domain.Common; 4 | 5 | namespace CqrsWithEs.Tests 6 | { 7 | public class CarsTestData 8 | { 9 | public static Car OldFordFocus() 10 | { 11 | return new Car 12 | ( 13 | "Ford Focus", 14 | "WAW1010", 15 | 2005 16 | ); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /SeparateModels/Queries/GetPolicyVersionsListQuery.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using SeparateModels.ReadModels; 3 | 4 | namespace SeparateModels.Queries 5 | { 6 | public class GetPolicyVersionsListQuery : IRequest 7 | { 8 | public string PolicyNumber { get; set; } 9 | } 10 | 11 | public class GetPolicyVersionsListResult 12 | { 13 | public PolicyVersionsListDto Versions { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /NoCqrs/Services/ConfirmBuyAdditionalCoverRequest.cs: -------------------------------------------------------------------------------- 1 | namespace NoCqrs.Services 2 | { 3 | public class ConfirmBuyAdditionalCoverRequest 4 | { 5 | public string PolicyNumber { get; set; } 6 | public int VersionToConfirmNumber { get; set; } 7 | } 8 | 9 | public class ConfirmBuyAdditionalCoverResult 10 | { 11 | public string PolicyNumber { get; set; } 12 | public int VersionConfirmed { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /CqrsWithEs.Tests/TestData/PersonsTestData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CqrsWithEs.Domain; 3 | using CqrsWithEs.Domain.Common; 4 | 5 | namespace CqrsWithEs.Tests 6 | { 7 | public class PersonsTestData 8 | { 9 | public static Person Kowalski() 10 | { 11 | return new Person 12 | ( 13 | "Jan", 14 | "Kowalski", 15 | "1111111116" 16 | ); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Policy/UnitPrice.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NodaMoney; 3 | 4 | namespace CqrsWithEs.Domain.Policy 5 | { 6 | public class UnitPrice 7 | { 8 | public Money Price { get; private set; } 9 | public TimeSpan PricePeriod { get; private set; } 10 | 11 | public UnitPrice(Money price, TimeSpan pricePeriod) 12 | { 13 | Price = price; 14 | PricePeriod = pricePeriod; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /NoCqrs/Services/TerminatePolicyRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NoCqrs.Services 4 | { 5 | public class TerminatePolicyRequest 6 | { 7 | public string PolicyNumber { get; set; } 8 | public DateTime TerminationDate { get; set; } 9 | } 10 | 11 | public class TerminatePolicyResult 12 | { 13 | public string PolicyNumber { get; set; } 14 | public int VersionWithTerminationNumber { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Product/IProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.Threading.Tasks; 4 | 5 | namespace CqrsWithEs.Domain.Product 6 | { 7 | public interface IProductRepository 8 | { 9 | void Add(Domain.Product.Product product); 10 | 11 | Task WithCode(string code); 12 | 13 | Task> All(); 14 | } 15 | } -------------------------------------------------------------------------------- /CqrsWithEs/ReadModel/ReadModelInstaller.cs: -------------------------------------------------------------------------------- 1 | using CqrsWithEs.Init; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace CqrsWithEs.ReadModel 5 | { 6 | public static class ReadModelInstaller 7 | { 8 | public static IServiceCollection AddReadModels(this IServiceCollection services, string cnnString) 9 | { 10 | services.AddSingleton(new PolicyInfoDao(cnnString)); 11 | return services; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /NoCqrs/Services/SearchPolicyRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NoCqrs.Services 4 | { 5 | public class SearchPolicyRequest 6 | { 7 | public string PolicyNumber { get; set; } 8 | public DateTime? PolicyStartFrom { get; set; } 9 | public DateTime? PolicyStartTo { get; set; } 10 | public string CarPlateNumber { get; set; } 11 | public string PolicyHolderFirstName { get; set; } 12 | public string PolicyHolderLastName { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /SeparateModels/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace SeparateModels 5 | { 6 | public class Program 7 | { 8 | public static void Main(string[] args) 9 | { 10 | CreateWebHostBuilder(args).Build().Run(); 11 | } 12 | 13 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 14 | WebHost.CreateDefaultBuilder(args) 15 | .UseStartup(); 16 | } 17 | } -------------------------------------------------------------------------------- /NoCqrs/Services/PolicyInfoDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NoCqrs.Services 4 | { 5 | public class PolicyInfoDto 6 | { 7 | public Guid PolicyId { get; set; } 8 | public string PolicyNumber { get; set; } 9 | public DateTime CoverFrom { get; set; } 10 | public DateTime CoverTo { get; set; } 11 | public string Vehicle { get; set; } 12 | public string PolicyHolder { get; set; } 13 | public decimal TotalPremiumAmount { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /SeparateModels/Commands/CreatePolicyCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MediatR; 3 | 4 | namespace SeparateModels.Commands 5 | { 6 | public class CreatePolicyCommand : IRequest 7 | { 8 | public string OfferNumber { get; set; } 9 | public DateTime PurchaseDate { get; set; } 10 | public DateTime PolicyStartDate { get; set; } 11 | } 12 | 13 | public class CreatePolicyResult 14 | { 15 | public string PolicyNumber { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /CqrsWithEs/Commands/ConfirmTerminationCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MediatR; 3 | 4 | namespace CqrsWithEs.Commands 5 | { 6 | public class ConfirmTerminationCommand : IRequest 7 | { 8 | public Guid PolicyId { get; set; } 9 | public int VersionToConfirmNumber { get; set; } 10 | } 11 | 12 | public class ConfirmTerminationResult 13 | { 14 | public Guid PolicyId { get; set; } 15 | public int VersionConfirmed { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /CqrsWithEs/Commands/TerminatePolicyCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MediatR; 3 | 4 | namespace CqrsWithEs.Commands 5 | { 6 | public class TerminatePolicyCommand : IRequest 7 | { 8 | public Guid PolicyId { get; set; } 9 | public DateTime TerminationDate { get; set; } 10 | } 11 | 12 | public class TerminatePolicyResult 13 | { 14 | public Guid PolicyId { get; set; } 15 | public int VersionWithTerminationNumber { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Policy/IPolicyState.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using CqrsWithEs.Domain.Common; 4 | using NodaMoney; 5 | 6 | namespace CqrsWithEs.Domain.Policy 7 | { 8 | public interface IPolicyState 9 | { 10 | PolicyStatus PolicyStatus { get; } 11 | ValidityPeriod CoverPeriod { get; } 12 | ValidityPeriod VersionPeriod { get; } 13 | IReadOnlyCollection Covers { get; } 14 | Money TotalPremium { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /CqrsWithEs/ReadModel/PolicyInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CqrsWithEs.ReadModel 4 | { 5 | public class PolicyInfo 6 | { 7 | public Guid PolicyId { get; set; } 8 | public string PolicyNumber { get; set; } 9 | public DateTime CoverFrom { get; set; } 10 | public DateTime CoverTo { get; set; } 11 | public string Vehicle { get; set; } 12 | public string PolicyHolder { get; set; } 13 | public decimal TotalPremiumAmount { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /SeparateModels/Commands/ConfirmTerminationCommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace SeparateModels.Commands 4 | { 5 | public class ConfirmTerminationCommand : IRequest 6 | { 7 | public string PolicyNumber { get; set; } 8 | public int VersionToConfirmNumber { get; set; } 9 | } 10 | 11 | public class ConfirmTerminationResult 12 | { 13 | public string PolicyNumber { get; set; } 14 | public int VersionConfirmed { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /SeparateModels/ReadModels/PolicyInfoDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SeparateModels.ReadModels 4 | { 5 | public class PolicyInfoDto 6 | { 7 | public Guid PolicyId { get; set; } 8 | public string PolicyNumber { get; set; } 9 | public DateTime CoverFrom { get; set; } 10 | public DateTime CoverTo { get; set; } 11 | public string Vehicle { get; set; } 12 | public string PolicyHolder { get; set; } 13 | public decimal TotalPremiumAmount { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /SeparateModels/Commands/TerminatePolicyCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MediatR; 3 | 4 | namespace SeparateModels.Commands 5 | { 6 | public class TerminatePolicyCommand : IRequest 7 | { 8 | public string PolicyNumber { get; set; } 9 | public DateTime TerminationDate { get; set; } 10 | } 11 | 12 | public class TerminatePolicyResult 13 | { 14 | public string PolicyNumber { get; set; } 15 | public int VersionWithTerminationNumber { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /CqrsWithEs/Commands/ConfirmBuyAdditionalCoverCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MediatR; 3 | 4 | namespace CqrsWithEs.Commands 5 | { 6 | public class ConfirmBuyAdditionalCoverCommand : IRequest 7 | { 8 | public Guid PolicyId { get; set; } 9 | public int VersionToConfirmNumber { get; set; } 10 | } 11 | 12 | public class ConfirmBuyAdditionalCoverResult 13 | { 14 | public Guid PolicyId { get; set; } 15 | public int VersionConfirmed { get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /SeparateModels/Commands/ConfirmBuyAdditionalCoverCommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace SeparateModels.Commands 4 | { 5 | public class ConfirmBuyAdditionalCoverCommand : IRequest 6 | { 7 | public string PolicyNumber { get; set; } 8 | public int VersionToConfirmNumber { get; set; } 9 | } 10 | 11 | public class ConfirmBuyAdditionalCoverResult 12 | { 13 | public string PolicyNumber { get; set; } 14 | public int VersionConfirmed { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /CqrsWithEs/Commands/CreatePolicyCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MediatR; 3 | 4 | namespace CqrsWithEs.Commands 5 | { 6 | public class CreatePolicyCommand : IRequest 7 | { 8 | public string OfferNumber { get; set; } 9 | public DateTime PurchaseDate { get; set; } 10 | public DateTime PolicyStartDate { get; set; } 11 | } 12 | 13 | public class CreatePolicyResult 14 | { 15 | public Guid PolicyId { get; set; } 16 | public string PolicyNumber { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /NoCqrs/Domain/UnitPrice.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NodaMoney; 3 | 4 | namespace NoCqrs.Domain 5 | { 6 | public class UnitPrice 7 | { 8 | public Money Value { get; private set; } 9 | public TimeSpan Unit { get; private set; } 10 | 11 | public UnitPrice(Money value, TimeSpan unit) 12 | { 13 | Value = value; 14 | Unit = unit; 15 | } 16 | 17 | public Money Multiply(TimeSpan qt) 18 | { 19 | return (qt.Days / qt.Days) * Value; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /SeparateModels/Domain/UnitPrice.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NodaMoney; 3 | 4 | namespace SeparateModels.Domain 5 | { 6 | public class UnitPrice 7 | { 8 | public Money Value { get; private set; } 9 | public TimeSpan Unit { get; private set; } 10 | 11 | public UnitPrice(Money value, TimeSpan unit) 12 | { 13 | Value = value; 14 | Unit = unit; 15 | } 16 | 17 | public Money Multiply(TimeSpan qt) 18 | { 19 | return (qt.Days / qt.Days) * Value; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /NoCqrs/Domain/Cover.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NoCqrs.Domain 4 | { 5 | public class Cover 6 | { 7 | public Guid Id { get; private set; } 8 | public string Code { get; private set; } 9 | public string Name { get; private set; } 10 | 11 | public Cover(Guid id, string code, string name) 12 | { 13 | Id = id; 14 | Code = code; 15 | Name = name; 16 | } 17 | 18 | //required by EF 19 | protected Cover() 20 | { 21 | } 22 | } 23 | 24 | 25 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Policy/Events/TerminalPolicyVersionConfirmed.cs: -------------------------------------------------------------------------------- 1 | using CqrsWithEs.Domain.Base; 2 | 3 | namespace CqrsWithEs.Domain.Policy.Events 4 | { 5 | public class TerminalPolicyVersionConfirmed : Event 6 | { 7 | public int VersionNumber { get; private set; } 8 | public PolicyVersionStatus VersionStatus { get; private set; } 9 | 10 | public TerminalPolicyVersionConfirmed(int versionNumber) 11 | { 12 | VersionNumber = versionNumber; 13 | VersionStatus = PolicyVersionStatus.Active; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /SeparateModels/Domain/Cover.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SeparateModels.Domain 4 | { 5 | public class Cover 6 | { 7 | public Guid Id { get; private set; } 8 | public string Code { get; private set; } 9 | public string Name { get; private set; } 10 | 11 | public Cover(Guid id, string code, string name) 12 | { 13 | Id = id; 14 | Code = code; 15 | Name = name; 16 | } 17 | 18 | //required by EF 19 | protected Cover() 20 | { 21 | } 22 | } 23 | 24 | 25 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Product/Cover.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CqrsWithEs.Domain.Product 4 | { 5 | public class Cover 6 | { 7 | public Guid Id { get; private set; } 8 | public string Code { get; private set; } 9 | public string Name { get; private set; } 10 | 11 | public Cover(Guid id, string code, string name) 12 | { 13 | Id = id; 14 | Code = code; 15 | Name = name; 16 | } 17 | 18 | //required by EF 19 | protected Cover() 20 | { 21 | } 22 | } 23 | 24 | 25 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Policy/Events/CoverageExtendedPolicyVersionConfirmed.cs: -------------------------------------------------------------------------------- 1 | using CqrsWithEs.Domain.Base; 2 | 3 | namespace CqrsWithEs.Domain.Policy.Events 4 | { 5 | public class CoverageExtendedPolicyVersionConfirmed : Event 6 | { 7 | public int VersionNumber { get; private set; } 8 | public PolicyVersionStatus VersionStatus { get; private set; } 9 | 10 | public CoverageExtendedPolicyVersionConfirmed(int versionNumber) 11 | { 12 | VersionNumber = versionNumber; 13 | VersionStatus = PolicyVersionStatus.Active; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /SeparateModels/Queries/FindPoliciesQuery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MediatR; 4 | using SeparateModels.ReadModels; 5 | using SeparateModels.Services; 6 | 7 | namespace SeparateModels.Queries 8 | { 9 | public class FindPoliciesQuery : IRequest> 10 | { 11 | public string PolicyNumber { get; set; } 12 | public DateTime? PolicyStartFrom { get; set; } 13 | public DateTime? PolicyStartTo { get; set; } 14 | public string CarPlateNumber { get; set; } 15 | public string PolicyHolder{ get; set; } 16 | } 17 | } -------------------------------------------------------------------------------- /SeparateModels/ReadModels/PolicyVersionsListDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace SeparateModels.ReadModels 5 | { 6 | public class PolicyVersionsListDto 7 | { 8 | public string PolicyNumber { get; set; } 9 | public List VersionsInfo { get; set; } 10 | } 11 | 12 | public class PolicyVersionInfoDto 13 | { 14 | public int Number { get; set; } 15 | public DateTime VersionFrom { get; set; } 16 | public DateTime VersionTo { get; set; } 17 | public string VersionStatus { get; set; } 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /SeparateModels/Domain/Person.cs: -------------------------------------------------------------------------------- 1 | namespace SeparateModels.Domain 2 | { 3 | public class Person 4 | { 5 | public string FirstName { get; private set; } 6 | public string LastName { get; private set; } 7 | public string TaxId { get; private set; } 8 | 9 | public Person(string firstName, string lastName, string taxId) 10 | { 11 | FirstName = firstName; 12 | LastName = lastName; 13 | TaxId = taxId; 14 | } 15 | 16 | public Person Copy() 17 | { 18 | return new Person(FirstName,LastName,TaxId); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Common/Person.cs: -------------------------------------------------------------------------------- 1 | namespace CqrsWithEs.Domain.Common 2 | { 3 | public class Person 4 | { 5 | public string FirstName { get; private set; } 6 | public string LastName { get; private set; } 7 | public string TaxId { get; private set; } 8 | 9 | public Person(string firstName, string lastName, string taxId) 10 | { 11 | FirstName = firstName; 12 | LastName = lastName; 13 | TaxId = taxId; 14 | } 15 | 16 | public Person Copy() 17 | { 18 | return new Person(FirstName,LastName,TaxId); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /NoCqrs.Tests/NoCqrs.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /NoCqrs/Domain/Person.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NoCqrs.Domain 4 | { 5 | public class Person 6 | { 7 | public string FirstName { get; private set; } 8 | public string LastName { get; private set; } 9 | public string TaxId { get; private set; } 10 | 11 | public Person(string firstName, string lastName, string taxId) 12 | { 13 | FirstName = firstName; 14 | LastName = lastName; 15 | TaxId = taxId; 16 | } 17 | 18 | public Person Copy() 19 | { 20 | return new Person(FirstName,LastName,TaxId); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /NoCqrs/NoCqrs.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /NoCqrs/Services/BuyAdditionalCoverRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace NoCqrs.Services 4 | { 5 | public class BuyAdditionalCoverRequest 6 | { 7 | public string PolicyNumber { get; set; } 8 | public DateTime EffectiveDateOfChange { get; set; } 9 | public string NewCoverCode { get; set; } 10 | public decimal NewCoverPrice { get; set; } 11 | public TimeSpan NewCoverPriceUnit { get; set; } 12 | } 13 | 14 | public class BuyAdditionalCoverResult 15 | { 16 | public string PolicyNumber { get; set; } 17 | public int VersionWithAdditionalCoversNumber { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /SeparateModels/Domain/Car.cs: -------------------------------------------------------------------------------- 1 | namespace SeparateModels.Domain 2 | { 3 | public class Car 4 | { 5 | public string Make { get; private set; } 6 | public string PlateNumber { get; private set; } 7 | public int ProductionYear { get; private set; } 8 | 9 | public Car(string make, string plateNumber, int productionYear) 10 | { 11 | Make = make; 12 | PlateNumber = plateNumber; 13 | ProductionYear = productionYear; 14 | } 15 | 16 | public Car Copy() 17 | { 18 | return new Car(Make, PlateNumber, ProductionYear); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Common/Car.cs: -------------------------------------------------------------------------------- 1 | namespace CqrsWithEs.Domain.Common 2 | { 3 | public class Car 4 | { 5 | public string Make { get; private set; } 6 | public string PlateNumber { get; private set; } 7 | public int ProductionYear { get; private set; } 8 | 9 | public Car(string make, string plateNumber, int productionYear) 10 | { 11 | Make = make; 12 | PlateNumber = plateNumber; 13 | ProductionYear = productionYear; 14 | } 15 | 16 | public Car Copy() 17 | { 18 | return new Car(Make, PlateNumber, ProductionYear); 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /CqrsWithEs/Commands/BuyAdditionalCoverCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MediatR; 3 | 4 | namespace CqrsWithEs.Commands 5 | { 6 | public class BuyAdditionalCoverCommand : IRequest 7 | { 8 | public Guid PolicyId { get; set; } 9 | public DateTime EffectiveDateOfChange { get; set; } 10 | public string NewCoverCode { get; set; } 11 | public decimal NewCoverPrice { get; set; } 12 | public TimeSpan NewCoverPriceUnit { get; set; } 13 | } 14 | 15 | public class BuyAdditionalCoverResult 16 | { 17 | public string PolicyNumber { get; set; } 18 | public int VersionWithAdditionalCoversNumber { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /NoCqrs/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace NoCqrs 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup(); 23 | } 24 | } -------------------------------------------------------------------------------- /CqrsWithEs/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace CqrsWithEs 12 | { 13 | public class Program 14 | { 15 | public static void Main(string[] args) 16 | { 17 | CreateWebHostBuilder(args).Build().Run(); 18 | } 19 | 20 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) => 21 | WebHost.CreateDefaultBuilder(args) 22 | .UseStartup(); 23 | } 24 | } -------------------------------------------------------------------------------- /SeparateModels/Commands/BuyAdditionalCoverCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MediatR; 3 | 4 | namespace SeparateModels.Commands 5 | { 6 | public class BuyAdditionalCoverCommand : IRequest 7 | { 8 | public string PolicyNumber { get; set; } 9 | public DateTime EffectiveDateOfChange { get; set; } 10 | public string NewCoverCode { get; set; } 11 | public decimal NewCoverPrice { get; set; } 12 | public TimeSpan NewCoverPriceUnit { get; set; } 13 | } 14 | 15 | public class BuyAdditionalCoverResult 16 | { 17 | public string PolicyNumber { get; set; } 18 | public int VersionWithAdditionalCoversNumber { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /SeparateModels/Installers/EFInstaller.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using SeparateModels.Domain; 5 | 6 | namespace SeparateModels.Installers 7 | { 8 | public static class EFInstaller 9 | { 10 | /*public static IServiceCollection AddEF(this IServiceCollection services, IConfiguration configuration) 11 | { 12 | services.AddDbContext(opts => 13 | { 14 | opts.UseInMemoryDatabase("InsuranceDb"); 15 | }); 16 | services.AddScoped(); 17 | return services; 18 | }*/ 19 | } 20 | } -------------------------------------------------------------------------------- /NoCqrs/Domain/Car.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; 3 | 4 | namespace NoCqrs.Domain 5 | { 6 | public class Car 7 | { 8 | public string Make { get; private set; } 9 | public string PlateNumber { get; private set; } 10 | public int ProductionYear { get; private set; } 11 | 12 | public Car(string make, string plateNumber, int productionYear) 13 | { 14 | Make = make; 15 | PlateNumber = plateNumber; 16 | ProductionYear = productionYear; 17 | } 18 | 19 | public Car Copy() 20 | { 21 | return new Car(Make, PlateNumber, ProductionYear); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /NoCqrs/Installers/EFInstaller.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using NoCqrs.DataAccess; 5 | using NoCqrs.Domain; 6 | 7 | namespace NoCqrs.Installers 8 | { 9 | public static class EFInstaller 10 | { 11 | public static IServiceCollection AddEF(this IServiceCollection services, IConfiguration configuration) 12 | { 13 | services.AddDbContext(opts => 14 | { 15 | opts.UseInMemoryDatabase("InsuranceDb"); 16 | }); 17 | services.AddScoped(); 18 | return services; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /CqrsWithEs/DataAccess/DataAccessInstaller.cs: -------------------------------------------------------------------------------- 1 | using CqrsWithEs.Domain.Offer; 2 | using CqrsWithEs.Domain.Policy; 3 | using CqrsWithEs.Domain.Product; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace CqrsWithEs.DataAccess 7 | { 8 | public static class DataAccessInstaller 9 | { 10 | public static void AddDataAccess(this IServiceCollection services) 11 | { 12 | services.AddSingleton(); 13 | services.AddSingleton(); 14 | services.AddScoped(); 15 | services.AddSingleton(); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /NoCqrs/Init/Products.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NoCqrs.Domain; 4 | 5 | namespace NoCqrs.Init 6 | { 7 | public class Products 8 | { 9 | public static Product StandardCarInsurance() 10 | { 11 | return new Product 12 | ( 13 | Guid.NewGuid(), 14 | "STD_CAR_INSURANCE", 15 | "Standard Car Insurance", 16 | new List 17 | { 18 | new Cover(Guid.NewGuid(), "OC", "Third party liability"), 19 | new Cover(Guid.NewGuid(), "AC", "Auto casco"), 20 | new Cover(Guid.NewGuid(), "ASSISTANCE", "Assistance") 21 | } 22 | ); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /SeparateModels/Init/Products.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using SeparateModels.Domain; 4 | 5 | namespace SeparateModels.Init 6 | { 7 | public class Products 8 | { 9 | public static Product StandardCarInsurance() 10 | { 11 | return new Product 12 | ( 13 | Guid.NewGuid(), 14 | "STD_CAR_INSURANCE", 15 | "Standard Car Insurance", 16 | new List 17 | { 18 | new Cover(Guid.NewGuid(), "OC", "Third party liability"), 19 | new Cover(Guid.NewGuid(), "AC", "Auto casco"), 20 | new Cover(Guid.NewGuid(), "ASSISTANCE", "Assistance") 21 | } 22 | ); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /NoCqrs.Tests/TestData/ProductsTestData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NoCqrs.Domain; 4 | 5 | namespace NoCqrs.Tests 6 | { 7 | public class ProductsTestData 8 | { 9 | public static Product StandardCarInsurance() 10 | { 11 | return new Product 12 | ( 13 | Guid.NewGuid(), 14 | "STD_CAR_INSURANCE", 15 | "Standard Car Insurance", 16 | new List 17 | { 18 | new Cover(Guid.NewGuid(), "OC", "Third party liability"), 19 | new Cover(Guid.NewGuid(), "AC", "Auto casco"), 20 | new Cover(Guid.NewGuid(), "ASSISTANCE", "Assistance") 21 | } 22 | ); 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /CqrsWithEs.Tests/TestData/ProductsTestData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using CqrsWithEs.Domain; 4 | using CqrsWithEs.Domain.Product; 5 | 6 | namespace CqrsWithEs.Tests 7 | { 8 | public class ProductsTestData 9 | { 10 | public static Product StandardCarInsurance() 11 | { 12 | return new Product 13 | ( 14 | Guid.NewGuid(), 15 | "STD_CAR_INSURANCE", 16 | "Standard Car Insurance", 17 | new List 18 | { 19 | new Cover(Guid.NewGuid(), "OC", "Third party liability"), 20 | new Cover(Guid.NewGuid(), "AC", "Auto casco"), 21 | new Cover(Guid.NewGuid(), "ASSISTANCE", "Assistance") 22 | } 23 | ); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /CqrsWithEs/Init/DataLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using CqrsWithEs.Domain.Common; 5 | using CqrsWithEs.Domain.Offer; 6 | using CqrsWithEs.Domain.Product; 7 | using NodaMoney; 8 | 9 | namespace CqrsWithEs.Init 10 | { 11 | public class DataLoader 12 | { 13 | private readonly IOfferRepository offerRepository; 14 | 15 | public DataLoader(IOfferRepository offerRepository) 16 | { 17 | this.offerRepository = offerRepository; 18 | } 19 | 20 | public async Task Seed() 21 | { 22 | var existing = await offerRepository.WithNumber("OFF001"); 23 | if (existing == null) 24 | { 25 | offerRepository.Add(SampleOfferData.SampleOffer("OFF001")); 26 | } 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /NoCqrs.Tests/TestData/PolicyTestData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NoCqrs.Domain; 3 | 4 | namespace NoCqrs.Tests 5 | { 6 | public static class PolicyTestData 7 | { 8 | public static Policy StandardOneYearPolicy(DateTime policyStartDate) 9 | { 10 | var offer = OffersTestData.StandardOneYearOCOfferValidUntil(policyStartDate.AddDays(10)); 11 | return Policy.ConvertOffer(offer, "POL0001", policyStartDate.AddDays(-1), policyStartDate); 12 | } 13 | 14 | public static Policy StandardOneYearPolicyTerminated(DateTime policyStartDate, DateTime policyTerminationDate) 15 | { 16 | var policy = StandardOneYearPolicy(policyStartDate); 17 | policy.TerminatePolicy(policyTerminationDate); 18 | policy.ConfirmChanges(2); 19 | 20 | return policy; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /CqrsWithEs/Init/DataLoaderInstaller.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace CqrsWithEs.Init 6 | { 7 | public static class DataLoaderInstaller 8 | { 9 | public static IServiceCollection AddDataInitializer(this IServiceCollection services) 10 | { 11 | services.AddScoped(); 12 | return services; 13 | } 14 | } 15 | 16 | public static class ApplicationBuilderExtensions 17 | { 18 | public static async Task UseDataInitializer(this IApplicationBuilder app) 19 | { 20 | using (var scope = app.ApplicationServices.CreateScope()) 21 | { 22 | var initializer = scope.ServiceProvider.GetService(); 23 | await initializer.Seed(); 24 | } 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /NoCqrs/Domain/Product.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | 5 | namespace NoCqrs.Domain 6 | { 7 | public class Product 8 | { 9 | public Guid Id { get; private set; } 10 | public string Code { get; private set; } 11 | public string Name { get; private set; } 12 | private List covers = new List(); 13 | public IEnumerable Covers => covers.AsReadOnly(); 14 | 15 | public Product(Guid id, string code, string name, IList covers) 16 | { 17 | Id = id; 18 | Code = code; 19 | Name = name; 20 | foreach (var cover in covers) 21 | { 22 | this.covers.Add(cover); 23 | } 24 | } 25 | 26 | //required by EF 27 | protected Product() 28 | { 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /NoCqrs/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:45239", 8 | "sslPort": 44367 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "api/values", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "NoCqrs": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "api/values", 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /SeparateModels/ReadModels/PolicyFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace SeparateModels.ReadModels 4 | { 5 | public class PolicyFilter 6 | { 7 | public string PolicyNumber { get; private set; } 8 | public string PolicyHolder{ get; private set; } 9 | public DateTime? PolicyStartDateFrom { get; private set; } 10 | public DateTime? PolicyStartDateTo { get; private set; } 11 | public string CarPlateNumber { get; private set; } 12 | 13 | public PolicyFilter(string policyNumber, string policyHolder, DateTime? policyStartDateFrom, DateTime? policyStartDateTo, string carPlateNumber) 14 | { 15 | PolicyNumber = policyNumber; 16 | PolicyHolder = policyHolder; 17 | PolicyStartDateFrom = policyStartDateFrom; 18 | PolicyStartDateTo = policyStartDateTo; 19 | CarPlateNumber = carPlateNumber; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /CqrsWithEs/CqrsWithEs.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | InProcess 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /CqrsWithEs/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:43467", 8 | "sslPort": 44365 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "api/values", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "CqrsWithEs": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "api/values", 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /SeparateModels/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:45239", 8 | "sslPort": 44367 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "api/values", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "NoCqrs": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "api/values", 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /NoCqrs/DataAccess/EFProductRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.EntityFrameworkCore; 4 | using NoCqrs.Domain; 5 | 6 | namespace NoCqrs.DataAccess 7 | { 8 | public class EFProductRepository : IProductRepository 9 | { 10 | private readonly InsuranceDbContext dbContext; 11 | 12 | public EFProductRepository(InsuranceDbContext dbContext) 13 | { 14 | this.dbContext = dbContext; 15 | } 16 | 17 | public void Add(Product product) 18 | { 19 | dbContext.Products.Add(product); 20 | } 21 | 22 | public Product WithCode(string code) 23 | { 24 | return dbContext.Products.Include(p => p.Covers).FirstOrDefault(p => p.Code == code); 25 | } 26 | 27 | public List All() 28 | { 29 | return dbContext.Products.Include(p => p.Covers).ToList(); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /SeparateModels/SeparateModels.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /SeparateModels/Domain/Product.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Newtonsoft.Json; 4 | 5 | namespace SeparateModels.Domain 6 | { 7 | public class Product 8 | { 9 | public Guid Id { get; private set; } 10 | public string Code { get; private set; } 11 | public string Name { get; private set; } 12 | [JsonProperty] 13 | private List covers = new List(); 14 | [JsonIgnore] 15 | public IEnumerable Covers => covers.AsReadOnly(); 16 | 17 | public Product(Guid id, string code, string name, IList covers) 18 | { 19 | Id = id; 20 | Code = code; 21 | Name = name; 22 | foreach (var cover in covers) 23 | { 24 | this.covers.Add(cover); 25 | } 26 | } 27 | 28 | //required by JSON 29 | public Product() 30 | { 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /SeparateModels/DataAccess/Marten/ProtectedSettersContractResolver.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Serialization; 4 | 5 | namespace SeparateModels.DataAccess.Marten 6 | { 7 | public class ProtectedSettersContractResolver : DefaultContractResolver 8 | { 9 | public ProtectedSettersContractResolver() 10 | { 11 | } 12 | 13 | protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization) 14 | { 15 | var prop = base.CreateProperty(member, memberSerialization); 16 | 17 | if (!prop.Writable) 18 | { 19 | var property = member as PropertyInfo; 20 | if (property != null) 21 | { 22 | var hasSetter = property.GetSetMethod(true) != null; 23 | prop.Writable = hasSetter; 24 | } 25 | } 26 | 27 | return prop; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /CqrsWithEs/DataAccess/InMemoryPolicyRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CqrsWithEs.Domain.Policy; 3 | using MediatR; 4 | 5 | 6 | namespace CqrsWithEs.DataAccess 7 | { 8 | public class InMemoryPolicyRepository : IPolicyRepository 9 | { 10 | private readonly IEventStore eventStore; 11 | private readonly IMediator bus; 12 | 13 | public InMemoryPolicyRepository(IEventStore eventStore, IMediator bus) 14 | { 15 | this.eventStore = eventStore; 16 | this.bus = bus; 17 | this.eventStore.Bus = bus; 18 | } 19 | 20 | public Policy GetById(Guid Id) 21 | { 22 | var events = eventStore.GetEventsForAggregate(Id); 23 | return new Policy(Id, events); 24 | } 25 | 26 | public void Save(Policy policy, int expectedVersion) 27 | { 28 | eventStore.SaveEvents(policy.Id, policy.GetUncommittedChanges(), expectedVersion); 29 | policy.MarkChangesAsCommitted(); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /CqrsWithEs/DataAccess/InMemoryProductsRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Collections.ObjectModel; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using CqrsWithEs.Domain.Product; 8 | 9 | namespace CqrsWithEs.DataAccess 10 | { 11 | public class InMemoryProductsRepository : IProductRepository 12 | { 13 | private readonly IDictionary products = new ConcurrentDictionary(); 14 | 15 | public void Add(Product product) 16 | { 17 | products.Add(product.Code, product); 18 | } 19 | 20 | public Task WithCode(string code) 21 | { 22 | return Task.FromResult(products[code]); 23 | } 24 | 25 | public Task> All() 26 | { 27 | var allProducts = products.Values.ToList().AsReadOnly(); 28 | return Task.FromResult(allProducts); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /CqrsWithEs/Init/SampleOfferData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using CqrsWithEs.Domain.Common; 4 | using CqrsWithEs.Domain.Offer; 5 | using CqrsWithEs.Domain.Product; 6 | using NodaMoney; 7 | 8 | namespace CqrsWithEs.Init 9 | { 10 | public class SampleOfferData 11 | { 12 | public static Offer SampleOffer(string offerNumber) 13 | { 14 | return new Offer 15 | ( 16 | Guid.NewGuid(), 17 | offerNumber, 18 | new Product(Guid.NewGuid(),"OC", "OC", new List {}), 19 | new Person("Jan", "Nowak", "11111111116"), 20 | new Person("Jan", "Nowak", "11111111116"), 21 | new Car("Ford Escort","WAW1010",2001), 22 | TimeSpan.FromDays(365), 23 | Money.Euro(350), 24 | new DateTime(2019,2,25), 25 | new Dictionary 26 | { 27 | {new Cover(Guid.NewGuid(),"OC","OC"), Money.Euro(350)} 28 | } 29 | ); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /SeparateModels/Queries/GetPolicyVersionDetailsHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using MediatR; 4 | using SeparateModels.Domain; 5 | using SeparateModels.ReadModels; 6 | using SeparateModels.Services; 7 | 8 | namespace SeparateModels.Queries 9 | { 10 | public class GetPolicyVersionDetailsHandler : IRequestHandler 11 | { 12 | private readonly PolicyVersionDtoFinder policyVersionDtoFinder; 13 | 14 | public GetPolicyVersionDetailsHandler(PolicyVersionDtoFinder policyVersionDtoFinder) 15 | { 16 | this.policyVersionDtoFinder = policyVersionDtoFinder; 17 | } 18 | 19 | public Task Handle(GetPolicyVersionDetailsQuery query, CancellationToken cancellationToken) 20 | { 21 | var policyVersion = policyVersionDtoFinder.FindByPolicyNumberAndVersionNumber 22 | ( 23 | query.PolicyNumber, 24 | query.VersionNumber 25 | ); 26 | return Task.FromResult(policyVersion); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /SeparateModels/Queries/GetPolicyVersionsListHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using MediatR; 4 | using SeparateModels.ReadModels; 5 | 6 | namespace SeparateModels.Queries 7 | { 8 | public class GetPolicyVersionsListHandler : IRequestHandler 9 | { 10 | private readonly PolicyVersionDtoFinder policyVersionDtoFinder; 11 | 12 | public GetPolicyVersionsListHandler(PolicyVersionDtoFinder policyVersionDtoFinder) 13 | { 14 | this.policyVersionDtoFinder = policyVersionDtoFinder; 15 | } 16 | 17 | public Task Handle(GetPolicyVersionsListQuery query, CancellationToken cancellationToken) 18 | { 19 | var versions = policyVersionDtoFinder.FindVersionsListByPolicyNumber(query.PolicyNumber); 20 | 21 | return Task.FromResult 22 | ( 23 | new GetPolicyVersionsListResult 24 | { 25 | Versions = versions 26 | } 27 | ); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /NoCqrs/DataAccess/EFDataStore.cs: -------------------------------------------------------------------------------- 1 | using System.Security.Permissions; 2 | using NoCqrs.Domain; 3 | 4 | namespace NoCqrs.DataAccess 5 | { 6 | public class EFDataStore : IDataStore 7 | { 8 | private readonly InsuranceDbContext dbContext; 9 | private readonly IOfferRepository offerRepository; 10 | private readonly IProductRepository productRepository; 11 | private readonly IPolicyRepository policyRepository; 12 | 13 | public IOfferRepository Offers => offerRepository; 14 | public IProductRepository Products => productRepository; 15 | public IPolicyRepository Policies => policyRepository; 16 | 17 | public EFDataStore(InsuranceDbContext dbContext) 18 | { 19 | this.dbContext = dbContext; 20 | this.offerRepository = new EFOfferRepository(this.dbContext); 21 | this.productRepository = new EFProductRepository(this.dbContext); 22 | this.policyRepository = new EFPolicyRepository(this.dbContext); 23 | } 24 | 25 | public void CommitChanges() 26 | { 27 | dbContext.SaveChanges(); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /CqrsWithEs/DataAccess/InMemoryOfferRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Concurrent; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using CqrsWithEs.Domain.Offer; 7 | using CqrsWithEs.Init; 8 | 9 | namespace CqrsWithEs.DataAccess 10 | { 11 | public class InMemoryOfferRepository : IOfferRepository 12 | { 13 | private readonly IDictionary offers = new ConcurrentDictionary(); 14 | 15 | public InMemoryOfferRepository() 16 | { 17 | var offer001 = SampleOfferData.SampleOffer("OFF001"); 18 | offers.Add(offer001.Number, offer001); 19 | } 20 | 21 | public Task WithNumber(string number) 22 | { 23 | return Task.FromResult(offers[number]); 24 | } 25 | 26 | public Task> All() 27 | { 28 | return Task.FromResult(offers.Values.ToList().AsReadOnly()); 29 | } 30 | 31 | public void Add(Offer offer) 32 | { 33 | offers.Add(offer.Number, offer); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Product/Product.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Newtonsoft.Json; 5 | 6 | namespace CqrsWithEs.Domain.Product 7 | { 8 | public class Product 9 | { 10 | public Guid Id { get; private set; } 11 | public string Code { get; private set; } 12 | public string Name { get; private set; } 13 | [JsonProperty] 14 | private List covers = new List(); 15 | [JsonIgnore] 16 | public IEnumerable Covers => covers.AsReadOnly(); 17 | 18 | public Product(Guid id, string code, string name, IList covers) 19 | { 20 | Id = id; 21 | Code = code; 22 | Name = name; 23 | foreach (var cover in covers) 24 | { 25 | this.covers.Add(cover); 26 | } 27 | } 28 | 29 | //required by JSON 30 | public Product() 31 | { 32 | } 33 | 34 | public Cover CoverWithCode(string coverCode) 35 | { 36 | return covers.FirstOrDefault(c => c.Code == coverCode); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /CqrsWithEs.Tests/CqrsWithEs.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.2 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /SeparateModels/ReadModels/ReadModelsInstaller.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.DependencyInjection; 2 | 3 | namespace SeparateModels.ReadModels 4 | { 5 | public static class ReadModelsInstaller 6 | { 7 | public static IServiceCollection AddReadModels(this IServiceCollection services, string cnnString) 8 | { 9 | services 10 | .AddProjections(cnnString) 11 | .AddFinders(cnnString); 12 | 13 | return services; 14 | } 15 | 16 | private static IServiceCollection AddProjections(this IServiceCollection services, string cnnString) 17 | { 18 | services.AddSingleton(new PolicyInfoDtoProjection(cnnString)); 19 | services.AddSingleton(new PolicyVersionDtoProjection(cnnString)); 20 | return services; 21 | } 22 | 23 | private static IServiceCollection AddFinders(this IServiceCollection services, string cnnString) 24 | { 25 | services.AddSingleton(new PolicyInfoDtoFinder(cnnString)); 26 | services.AddSingleton(new PolicyVersionDtoFinder(cnnString)); 27 | return services; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /CqrsWithEs/Commands/ConfirmTerminationHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using CqrsWithEs.Domain.Policy; 4 | using MediatR; 5 | 6 | namespace CqrsWithEs.Commands 7 | { 8 | public class ConfirmTerminationHandler : IRequestHandler 9 | { 10 | private readonly IPolicyRepository policyRepository; 11 | 12 | public ConfirmTerminationHandler(IPolicyRepository policyRepository) 13 | { 14 | this.policyRepository = policyRepository; 15 | } 16 | 17 | public Task Handle(ConfirmTerminationCommand request, CancellationToken cancellationToken) 18 | { 19 | var policy = policyRepository.GetById(request.PolicyId); 20 | 21 | policy.ConfirmTermination(); 22 | 23 | policyRepository.Save(policy, policy.Version); 24 | 25 | return Task.FromResult(new ConfirmTerminationResult 26 | { 27 | PolicyId = policy.Id, 28 | VersionConfirmed = policy.Versions.LastActive().VersionNumber 29 | }); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /NoCqrs/DataAccess/EFOfferRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using Microsoft.EntityFrameworkCore; 4 | using NoCqrs.Domain; 5 | 6 | namespace NoCqrs.DataAccess 7 | { 8 | public class EFOfferRepository : IOfferRepository 9 | { 10 | private readonly InsuranceDbContext dbContext; 11 | 12 | public EFOfferRepository(InsuranceDbContext dbContext) 13 | { 14 | this.dbContext = dbContext; 15 | } 16 | 17 | public Offer WithNumber(string number) 18 | { 19 | return dbContext 20 | .Offers 21 | .Include(o=>o.Covers).ThenInclude(c => c.Cover) 22 | .Include(p => p.Product) 23 | .FirstOrDefault(o => o.Number == number); 24 | } 25 | 26 | public List All() 27 | { 28 | return dbContext 29 | .Offers 30 | .Include(o => o.Covers).ThenInclude(c => c.Cover) 31 | .Include(p => p.Product) 32 | .ToList(); 33 | } 34 | 35 | 36 | public void Add(Offer offer) 37 | { 38 | dbContext.Offers.Add(offer); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /NoCqrs/Services/PolicyInfoDtoAssembler.cs: -------------------------------------------------------------------------------- 1 | using NoCqrs.Domain; 2 | 3 | namespace NoCqrs.Services 4 | { 5 | public class PolicyInfoDtoAssembler 6 | { 7 | public static PolicyInfoDto AssemblePolicyInfoDto(Policy policy, PolicyVersion policyVersion) 8 | { 9 | return new PolicyInfoDto 10 | { 11 | PolicyId = policy.Id, 12 | PolicyNumber = policy.Number, 13 | CoverFrom = policyVersion.CoverPeriod.ValidFrom, 14 | CoverTo = policyVersion.CoverPeriod.ValidTo, 15 | Vehicle = AssembleVehicleDescription(policyVersion.Car), 16 | PolicyHolder = AssemblePolicyHolderDescription(policyVersion.PolicyHolder), 17 | TotalPremiumAmount = policyVersion.TotalPremium.Amount 18 | }; 19 | } 20 | 21 | private static string AssemblePolicyHolderDescription(Person policyHolder) 22 | { 23 | return $"{policyHolder.LastName} {policyHolder.FirstName}"; 24 | } 25 | 26 | private static string AssembleVehicleDescription(Car car) 27 | { 28 | return $"{car.Make} {car.ProductionYear} {car.PlateNumber}"; 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Policy/PolicyVersions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace CqrsWithEs.Domain.Policy 6 | { 7 | public static class PolicyVersions 8 | { 9 | public static PolicyVersion EffectiveAt(this IEnumerable versions, DateTime effectiveDateOfChange) 10 | { 11 | return versions 12 | .Where(v => v.VersionStatus == PolicyVersionStatus.Active) 13 | .Where(v => v.IsEffectiveOn(effectiveDateOfChange)) 14 | .OrderByDescending(v => v.VersionNumber) 15 | .FirstOrDefault(); 16 | } 17 | 18 | public static PolicyVersion WithNumber(this IEnumerable versions, int versionNumber) 19 | { 20 | return versions.FirstOrDefault(v => v.VersionNumber == versionNumber); 21 | } 22 | 23 | public static PolicyVersion LastActive(this IEnumerable versions) 24 | { 25 | return versions 26 | .Where(v => v.VersionStatus == PolicyVersionStatus.Active) 27 | .OrderByDescending(v => v.VersionNumber) 28 | .FirstOrDefault(); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /NoCqrs/Domain/ValueObject.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace NoCqrs.Domain 5 | { 6 | public abstract class ValueObject where T : ValueObject 7 | { 8 | protected abstract IEnumerable GetAttributesToIncludeInEqualityCheck(); 9 | 10 | public override bool Equals(object other) 11 | { 12 | return Equals(other as T); 13 | } 14 | 15 | public override int GetHashCode() 16 | { 17 | return GetAttributesToIncludeInEqualityCheck().Aggregate(17, (current, obj) => current * 31 + (obj == null ? 0 : obj.GetHashCode())); 18 | } 19 | 20 | public bool Equals(T other) 21 | { 22 | if (other == null) return false; 23 | 24 | return GetAttributesToIncludeInEqualityCheck().SequenceEqual(other.GetAttributesToIncludeInEqualityCheck()); 25 | } 26 | 27 | public static bool operator ==(ValueObject left, ValueObject right) 28 | { 29 | return Equals(left, right); 30 | } 31 | 32 | public static bool operator !=(ValueObject left, ValueObject right) 33 | { 34 | return !(left == right); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /CqrsWithEs/Commands/ConfirmBuyAdditionalCoverHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using CqrsWithEs.Domain.Policy; 4 | using MediatR; 5 | 6 | namespace CqrsWithEs.Commands 7 | { 8 | public class ConfirmBuyAdditionalCoverHandler : IRequestHandler 9 | { 10 | private IPolicyRepository policyRepository; 11 | 12 | public ConfirmBuyAdditionalCoverHandler(IPolicyRepository policyRepository) 13 | { 14 | this.policyRepository = policyRepository; 15 | } 16 | 17 | public Task Handle(ConfirmBuyAdditionalCoverCommand request, CancellationToken cancellationToken) 18 | { 19 | var policy = policyRepository.GetById(request.PolicyId); 20 | 21 | policy.ConfirmCoverageExtension(); 22 | 23 | policyRepository.Save(policy, policy.Version); 24 | 25 | return Task.FromResult(new ConfirmBuyAdditionalCoverResult 26 | { 27 | PolicyId = policy.Id, 28 | VersionConfirmed = policy.Versions.LastActive().VersionNumber 29 | }); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /CqrsWithEs/Commands/TerminatePolicyHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using CqrsWithEs.Domain.Policy; 6 | using MediatR; 7 | 8 | namespace CqrsWithEs.Commands 9 | { 10 | public class TerminatePolicyHandler : IRequestHandler 11 | { 12 | private readonly IPolicyRepository policyRepository; 13 | 14 | public TerminatePolicyHandler(IPolicyRepository policyRepository) 15 | { 16 | this.policyRepository = policyRepository; 17 | } 18 | 19 | 20 | public Task Handle(TerminatePolicyCommand request, CancellationToken cancellationToken) 21 | { 22 | var policy = policyRepository.GetById(request.PolicyId); 23 | 24 | policy.TerminatePolicy(request.TerminationDate); 25 | 26 | policyRepository.Save(policy, policy.Version); 27 | 28 | return Task.FromResult(new TerminatePolicyResult 29 | { 30 | PolicyId = policy.Id, 31 | VersionWithTerminationNumber = policy.Versions.Last().VersionNumber 32 | }); 33 | } 34 | 35 | } 36 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Base/ValueObject.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace CqrsWithEs.Domain.Base 5 | { 6 | public abstract class ValueObject where T : ValueObject 7 | { 8 | protected abstract IEnumerable GetAttributesToIncludeInEqualityCheck(); 9 | 10 | public override bool Equals(object other) 11 | { 12 | return Equals(other as T); 13 | } 14 | 15 | public override int GetHashCode() 16 | { 17 | return GetAttributesToIncludeInEqualityCheck().Aggregate(17, (current, obj) => current * 31 + (obj == null ? 0 : obj.GetHashCode())); 18 | } 19 | 20 | public bool Equals(T other) 21 | { 22 | if (other == null) return false; 23 | 24 | return GetAttributesToIncludeInEqualityCheck().SequenceEqual(other.GetAttributesToIncludeInEqualityCheck()); 25 | } 26 | 27 | public static bool operator ==(ValueObject left, ValueObject right) 28 | { 29 | return Equals(left, right); 30 | } 31 | 32 | public static bool operator !=(ValueObject left, ValueObject right) 33 | { 34 | return !(left == right); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /SeparateModels/Domain/ValueObject.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | 4 | namespace SeparateModels.Domain 5 | { 6 | public abstract class ValueObject where T : ValueObject 7 | { 8 | protected abstract IEnumerable GetAttributesToIncludeInEqualityCheck(); 9 | 10 | public override bool Equals(object other) 11 | { 12 | return Equals(other as T); 13 | } 14 | 15 | public override int GetHashCode() 16 | { 17 | return GetAttributesToIncludeInEqualityCheck().Aggregate(17, (current, obj) => current * 31 + (obj == null ? 0 : obj.GetHashCode())); 18 | } 19 | 20 | public bool Equals(T other) 21 | { 22 | if (other == null) return false; 23 | 24 | return GetAttributesToIncludeInEqualityCheck().SequenceEqual(other.GetAttributesToIncludeInEqualityCheck()); 25 | } 26 | 27 | public static bool operator ==(ValueObject left, ValueObject right) 28 | { 29 | return Equals(left, right); 30 | } 31 | 32 | public static bool operator !=(ValueObject left, ValueObject right) 33 | { 34 | return !(left == right); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /SeparateModels/Commands/TerminatePolicyHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using System.Transactions; 5 | using MediatR; 6 | using Microsoft.AspNetCore.Authentication; 7 | using SeparateModels.Domain; 8 | using SeparateModels.Services; 9 | 10 | namespace SeparateModels.Commands 11 | { 12 | public class TerminatePolicyHandler : IRequestHandler 13 | { 14 | private readonly IDataStore dataStore; 15 | 16 | public TerminatePolicyHandler(IDataStore dataStore) 17 | { 18 | this.dataStore = dataStore; 19 | } 20 | 21 | public async Task Handle(TerminatePolicyCommand command, CancellationToken cancellationToken) 22 | { 23 | var policy = await dataStore.Policies.WithNumber(command.PolicyNumber); 24 | policy.TerminatePolicy(command.TerminationDate); 25 | 26 | await dataStore.CommitChanges(); 27 | 28 | return new TerminatePolicyResult 29 | { 30 | PolicyNumber = policy.Number, 31 | VersionWithTerminationNumber = policy.Versions.Last().VersionNumber 32 | }; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /SeparateModels/EventHandlers/PolicyAnnexedProjectionsHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using MediatR; 4 | using SeparateModels.Domain; 5 | using SeparateModels.ReadModels; 6 | 7 | namespace SeparateModels.EventHandlers 8 | { 9 | public class PolicyAnnexedProjectionsHandler : 10 | INotificationHandler 11 | { 12 | private readonly PolicyInfoDtoProjection policyInfoDtoProjection; 13 | private readonly PolicyVersionDtoProjection policyVersionDtoProjection; 14 | 15 | public PolicyAnnexedProjectionsHandler(PolicyInfoDtoProjection policyInfoDtoProjection, PolicyVersionDtoProjection policyVersionDtoProjection) 16 | { 17 | this.policyInfoDtoProjection = policyInfoDtoProjection; 18 | this.policyVersionDtoProjection = policyVersionDtoProjection; 19 | } 20 | 21 | public Task Handle(PolicyAnnexed @event, CancellationToken cancellationToken) 22 | { 23 | policyInfoDtoProjection.UpdatePolicyInfoDto(@event.AnnexedPolicy, @event.AnnexVersion); 24 | 25 | policyVersionDtoProjection.CreatePolicyVersionDtoProjection(@event.AnnexedPolicy, @event.AnnexVersion); 26 | 27 | return Task.CompletedTask; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /CqrsWithEs.Tests/Asserts/PolicyEventsStreamAsserts.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using CqrsWithEs.Domain.Base; 4 | using KellermanSoftware.CompareNetObjects; 5 | using Xunit; 6 | 7 | namespace CqrsWithEs.Tests.Asserts 8 | { 9 | public class PolicyEventsStreamAssert 10 | { 11 | private readonly IEnumerable events; 12 | 13 | public PolicyEventsStreamAssert(IEnumerable events) 14 | { 15 | this.events = events; 16 | } 17 | 18 | public PolicyEventsStreamAssert BeSingle() 19 | { 20 | Assert.Single(events); 21 | return this; 22 | } 23 | 24 | public PolicyEventsStreamAssert ContainEvent(T expectedEvent) where T : Event 25 | { 26 | var comparer = new CompareLogic {Config = {MembersToIgnore = new List {"Id", "Version"}}}; 27 | Assert.Contains(events, ev => comparer.Compare(ev, expectedEvent).AreEqual); 28 | return this; 29 | } 30 | } 31 | 32 | public static class PolicyEventsStreamAssertExtension 33 | { 34 | public static PolicyEventsStreamAssert Should(this IEnumerable events) 35 | { 36 | return new PolicyEventsStreamAssert(events); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /SeparateModels/EventHandlers/PolicyTerminatedProjectionsHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using MediatR; 4 | using SeparateModels.Domain; 5 | using SeparateModels.ReadModels; 6 | 7 | namespace SeparateModels.EventHandlers 8 | { 9 | public class PolicyTerminatedProjectionsHandler : INotificationHandler 10 | { 11 | private readonly PolicyInfoDtoProjection policyInfoDtoProjection; 12 | private readonly PolicyVersionDtoProjection policyVersionDtoProjection; 13 | 14 | public PolicyTerminatedProjectionsHandler(PolicyInfoDtoProjection policyInfoDtoProjection, PolicyVersionDtoProjection policyVersionDtoProjection) 15 | { 16 | this.policyInfoDtoProjection = policyInfoDtoProjection; 17 | this.policyVersionDtoProjection = policyVersionDtoProjection; 18 | } 19 | 20 | public Task Handle(PolicyTerminated @event, CancellationToken cancellationToken) 21 | { 22 | policyInfoDtoProjection.UpdatePolicyInfoDto(@event.TerminatedPolicy, @event.TerminatedVersion); 23 | 24 | policyVersionDtoProjection.CreatePolicyVersionDtoProjection(@event.TerminatedPolicy, @event.TerminatedVersion); 25 | 26 | return Task.CompletedTask; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /SeparateModels/Domain/PolicyVersions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace SeparateModels.Domain 6 | { 7 | public static class PolicyVersions 8 | { 9 | public static PolicyVersion EffectiveAtDate(this IEnumerable versions, DateTime effectiveDate) 10 | { 11 | return versions? 12 | .Where(v => v.IsEffectiveOn(effectiveDate) && v.IsActive()) 13 | .OrderByDescending(v => v.VersionNumber) 14 | .FirstOrDefault(); 15 | } 16 | 17 | public static int MaxVersionNumber(this IEnumerable versions) 18 | { 19 | return versions.Max(v => v.VersionNumber); 20 | } 21 | 22 | public static PolicyVersion WithNumber(this IEnumerable versions, int versionNumber) 23 | { 24 | return versions.FirstOrDefault(v => v.VersionNumber == versionNumber); 25 | } 26 | 27 | public static PolicyVersion LatestActive(this IEnumerable versions) 28 | { 29 | return versions 30 | .Where(v => v.VersionStatus == PolicyVersionStatus.Active) 31 | .OrderByDescending(v => v.VersionNumber) 32 | .FirstOrDefault(); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /SeparateModels/EventHandlers/PolicyChangesCancelledProjectionsHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using MediatR; 4 | using SeparateModels.Domain; 5 | using SeparateModels.ReadModels; 6 | 7 | namespace SeparateModels.EventHandlers 8 | { 9 | public class PolicyChangesCancelledProjectionsHandler : 10 | INotificationHandler 11 | { 12 | private readonly PolicyInfoDtoProjection policyInfoDtoProjection; 13 | private readonly PolicyVersionDtoProjection policyVersionDtoProjection; 14 | 15 | public PolicyChangesCancelledProjectionsHandler(PolicyInfoDtoProjection policyInfoDtoProjection, PolicyVersionDtoProjection policyVersionDtoProjection) 16 | { 17 | this.policyInfoDtoProjection = policyInfoDtoProjection; 18 | this.policyVersionDtoProjection = policyVersionDtoProjection; 19 | } 20 | 21 | public Task Handle(PolicyAnnexCancelled @event, CancellationToken cancellationToken) 22 | { 23 | policyInfoDtoProjection.UpdatePolicyInfoDto(@event.Policy, @event.CurrentVersionAfterAnnexCancellation); 24 | 25 | policyVersionDtoProjection.UpdatePolicyVersionDtoProjection(@event.CancelledAnnexVersion); 26 | 27 | return Task.CompletedTask; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /CqrsWithEs/ReadModel/PolicyVersionDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace CqrsWithEs.ReadModel 5 | { 6 | public class PolicyVersionDto 7 | { 8 | public Guid Id { get; set; } 9 | public Guid PolicyId { get; set; } 10 | public string PolicyNumber { get; set; } 11 | public string ProductCode { get; set; } 12 | public int VersionNumber { get; set; } 13 | public string VersionStatus { get; set; } 14 | public string PolicyStatus { get; set; } 15 | public string PolicyHolder { get; set; } 16 | public string Insured { get; set; } 17 | public string Car { get; set; } 18 | public DateTime CoverFrom { get; set; } 19 | public DateTime CoverTo { get; set; } 20 | public DateTime VersionFrom { get; set; } 21 | public DateTime VersionTo { get; set; } 22 | public decimal TotalPremium { get; set; } 23 | public List Covers { get; set; } 24 | public List Changes { get; set; } 25 | } 26 | 27 | public class CoverDto 28 | { 29 | public Guid Id { get; set; } 30 | public string Code { get; set; } 31 | public DateTime CoverFrom { get; set; } 32 | public DateTime CoverTo { get; set; } 33 | public decimal PremiumAmount { get; set; } 34 | } 35 | } -------------------------------------------------------------------------------- /NoCqrs/Domain/PolicyVersions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace NoCqrs.Domain 7 | { 8 | public static class PolicyVersions 9 | { 10 | public static PolicyVersion EffectiveAtDate(this IEnumerable versions, DateTime effectiveDate) 11 | { 12 | return versions? 13 | .Where(v => v.IsEffectiveOn(effectiveDate) && v.IsActive()) 14 | .OrderByDescending(v => v.VersionNumber) 15 | .FirstOrDefault(); 16 | } 17 | 18 | public static int MaxVersionNumber(this IEnumerable versions) 19 | { 20 | return versions.Max(v => v.VersionNumber); 21 | } 22 | 23 | public static PolicyVersion WithNumber(this IEnumerable versions, int versionNumber) 24 | { 25 | return versions.FirstOrDefault(v => v.VersionNumber == versionNumber); 26 | } 27 | 28 | public static PolicyVersion LatestActive(this IEnumerable versions) 29 | { 30 | return versions 31 | .Where(v => v.VersionStatus == PolicyVersionStatus.Active) 32 | .OrderByDescending(v => v.VersionNumber) 33 | .FirstOrDefault(); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /SeparateModels/ReadModels/PolicyVersionDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace SeparateModels.ReadModels 5 | { 6 | public class PolicyVersionDto 7 | { 8 | public Guid Id { get; set; } 9 | public Guid PolicyId { get; set; } 10 | public string PolicyNumber { get; set; } 11 | public string ProductCode { get; set; } 12 | public int VersionNumber { get; set; } 13 | public string VersionStatus { get; set; } 14 | public string PolicyStatus { get; set; } 15 | public string PolicyHolder { get; set; } 16 | public string Insured { get; set; } 17 | public string Car { get; set; } 18 | public DateTime CoverFrom { get; set; } 19 | public DateTime CoverTo { get; set; } 20 | public DateTime VersionFrom { get; set; } 21 | public DateTime VersionTo { get; set; } 22 | public decimal TotalPremium { get; set; } 23 | public List Covers { get; set; } 24 | public List Changes { get; set; } 25 | } 26 | 27 | public class CoverDto 28 | { 29 | public Guid Id { get; set; } 30 | public string Code { get; set; } 31 | public DateTime CoverFrom { get; set; } 32 | public DateTime CoverTo { get; set; } 33 | public decimal PremiumAmount { get; set; } 34 | } 35 | } -------------------------------------------------------------------------------- /CqrsWithEs/Commands/CreatePolicyHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using CqrsWithEs.Domain; 4 | using CqrsWithEs.Domain.Offer; 5 | using CqrsWithEs.Domain.Policy; 6 | using MediatR; 7 | 8 | namespace CqrsWithEs.Commands 9 | { 10 | public class CreatePolicyHandler : IRequestHandler 11 | { 12 | private readonly IOfferRepository offerRepository; 13 | private readonly IPolicyRepository policyRepository; 14 | 15 | public CreatePolicyHandler(IOfferRepository offerRepository, IPolicyRepository policyRepository) 16 | { 17 | this.offerRepository = offerRepository; 18 | this.policyRepository = policyRepository; 19 | } 20 | 21 | public async Task Handle(CreatePolicyCommand request, CancellationToken cancellationToken) 22 | { 23 | var offer = await offerRepository.WithNumber(request.OfferNumber); 24 | 25 | var policy = Policy.BuyOffer(offer, request.PurchaseDate, request.PolicyStartDate); 26 | 27 | policyRepository.Save(policy, -1); 28 | 29 | return new CreatePolicyResult 30 | { 31 | PolicyId = policy.Id, 32 | PolicyNumber = policy.PolicyNumber 33 | }; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /NoCqrs/Domain/PolicyCover.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NodaMoney; 3 | 4 | namespace NoCqrs.Domain 5 | { 6 | public class PolicyCover 7 | { 8 | public Guid Id { get; private set; } 9 | public Cover Cover { get; private set; } 10 | public ValidityPeriod CoverPeriod { get; private set; } 11 | 12 | public Money Price { get; private set; } 13 | public TimeSpan PricePeriod { get; private set; } 14 | 15 | public Money Amount { get; private set; } 16 | 17 | public PolicyCover(Guid id, Cover cover, ValidityPeriod coverPeriod, Money price, TimeSpan pricePeriod) 18 | { 19 | Id = id; 20 | Cover = cover; 21 | CoverPeriod = coverPeriod; 22 | Price = price; 23 | PricePeriod = pricePeriod; 24 | Amount = CalculateAmount(); 25 | } 26 | 27 | // required by EF 28 | protected PolicyCover() 29 | { 30 | } 31 | 32 | 33 | public void EndCoverOn(DateTime lastDayOfCover) 34 | { 35 | CoverPeriod = CoverPeriod.EndOn(lastDayOfCover); 36 | Amount = CalculateAmount(); 37 | } 38 | 39 | private Money CalculateAmount() 40 | { 41 | return decimal.Divide(CoverPeriod.Days, PricePeriod.Days) * Price; 42 | } 43 | 44 | } 45 | } -------------------------------------------------------------------------------- /NoCqrs/Domain/IPolicyRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace NoCqrs.Domain 6 | { 7 | public interface IPolicyRepository 8 | { 9 | Policy WithNumber(string number); 10 | 11 | void Add(Policy policy); 12 | 13 | IList Find(PolicyFilter filter); 14 | } 15 | 16 | public class PolicyFilter 17 | { 18 | public string PolicyNumber { get; private set; } 19 | public string PolicyHolderFirstName { get; private set; } 20 | public string PolicyHolderLastName { get; private set; } 21 | public DateTime? PolicyStartDateFrom { get; private set; } 22 | public DateTime? PolicyStartDateTo { get; private set; } 23 | public string CarPlateNumber { get; private set; } 24 | 25 | public PolicyFilter(string policyNumber, string policyHolderFirstName, string policyHolderLastName, DateTime? policyStartDateFrom, DateTime? policyStartDateTo, string carPlateNumber) 26 | { 27 | PolicyNumber = policyNumber; 28 | PolicyHolderFirstName = policyHolderFirstName; 29 | PolicyHolderLastName = policyHolderLastName; 30 | PolicyStartDateFrom = policyStartDateFrom; 31 | PolicyStartDateTo = policyStartDateTo; 32 | CarPlateNumber = carPlateNumber; 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /SeparateModels/EventHandlers/PolicyCreatedProjectionsHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using Dapper; 5 | using MediatR; 6 | using Npgsql; 7 | using SeparateModels.Domain; 8 | using SeparateModels.ReadModels; 9 | using SeparateModels.Services; 10 | 11 | namespace SeparateModels.EventHandlers 12 | { 13 | public class PolicyCreatedProjectionsHandler : 14 | INotificationHandler 15 | { 16 | private readonly PolicyInfoDtoProjection policyInfoDtoProjection; 17 | private readonly PolicyVersionDtoProjection policyVersionDtoProjection; 18 | 19 | public PolicyCreatedProjectionsHandler(PolicyInfoDtoProjection policyInfoDtoProjection, PolicyVersionDtoProjection policyVersionDtoProjection) 20 | { 21 | this.policyInfoDtoProjection = policyInfoDtoProjection; 22 | this.policyVersionDtoProjection = policyVersionDtoProjection; 23 | } 24 | 25 | public Task Handle(PolicyCreated @event, CancellationToken cancellationToken) 26 | { 27 | policyInfoDtoProjection.CreatePolicyInfoDto(@event.NewPolicy); 28 | 29 | policyVersionDtoProjection.CreatePolicyVersionDtoProjection(@event.NewPolicy, @event.NewPolicy.Versions.WithNumber(1)); 30 | 31 | return Task.CompletedTask; 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /SeparateModels/Commands/CreatePolicyHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using System.Transactions; 5 | using MediatR; 6 | using SeparateModels.Domain; 7 | 8 | namespace SeparateModels.Commands 9 | { 10 | public class CreatePolicyHandler : IRequestHandler 11 | { 12 | private readonly IDataStore dataStore; 13 | private readonly IMediator mediator; 14 | 15 | public CreatePolicyHandler(IDataStore dataStore, IMediator mediator) 16 | { 17 | this.dataStore = dataStore; 18 | this.mediator = mediator; 19 | } 20 | 21 | public async Task Handle(CreatePolicyCommand command, CancellationToken cancellationToken) 22 | { 23 | var offer = await dataStore.Offers.WithNumber(command.OfferNumber); 24 | var policy = Policy.ConvertOffer(offer, Guid.NewGuid().ToString(), command.PurchaseDate, 25 | command.PolicyStartDate); 26 | 27 | dataStore.Policies.Add(policy); 28 | await dataStore.CommitChanges(); 29 | 30 | await mediator.Publish(new PolicyCreated(policy)); 31 | 32 | return new CreatePolicyResult 33 | { 34 | PolicyNumber = policy.Number 35 | }; 36 | 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /SeparateModels/Domain/PolicyCover.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NodaMoney; 3 | 4 | namespace SeparateModels.Domain 5 | { 6 | public class PolicyCover 7 | { 8 | public Guid Id { get; private set; } 9 | public string CoverCode { get; private set; } 10 | public ValidityPeriod CoverPeriod { get; private set; } 11 | 12 | public Money Price { get; private set; } 13 | public TimeSpan PricePeriod { get; private set; } 14 | 15 | public Money Amount { get; private set; } 16 | 17 | public PolicyCover(Guid id, string coverCode, ValidityPeriod coverPeriod, Money price, TimeSpan pricePeriod) 18 | { 19 | Id = id; 20 | CoverCode = coverCode; 21 | CoverPeriod = coverPeriod; 22 | Price = price; 23 | PricePeriod = pricePeriod; 24 | Amount = CalculateAmount(); 25 | } 26 | 27 | // required by EF 28 | protected PolicyCover() 29 | { 30 | } 31 | 32 | 33 | public void EndCoverOn(DateTime lastDayOfCover) 34 | { 35 | CoverPeriod = CoverPeriod.EndOn(lastDayOfCover); 36 | Amount = CalculateAmount(); 37 | } 38 | 39 | private Money CalculateAmount() 40 | { 41 | return decimal.Divide(CoverPeriod.Days, PricePeriod.Days) * Price; 42 | } 43 | 44 | } 45 | } -------------------------------------------------------------------------------- /NoCqrs/Services/PolicyDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace NoCqrs.Services 5 | { 6 | public class PolicyDto 7 | { 8 | public Guid PolicyId { get; set; } 9 | 10 | public string PolicyNumber { get; set; } 11 | 12 | public PolicyVersionDto CurrentVersion { get; set; } 13 | 14 | public List Versions { get; set; } 15 | } 16 | 17 | public class PolicyVersionDto 18 | { 19 | public int VersionNumber { get; set; } 20 | public string VersionStatus { get; set; } 21 | public string PolicyStatus { get; set; } 22 | public string PolicyHolder { get; set; } 23 | public string Insured { get; set; } 24 | public string Car { get; set; } 25 | public DateTime CoverFrom { get; set; } 26 | public DateTime CoverTo { get; set; } 27 | public DateTime VersionFrom { get; set; } 28 | public DateTime VersionTo { get; set; } 29 | public decimal TotalPremium { get; set; } 30 | public List Covers { get; set; } 31 | public List Changes { get; set; } 32 | } 33 | 34 | public class CoverDto 35 | { 36 | public string Code { get; set; } 37 | public DateTime CoverFrom { get; set; } 38 | public DateTime CoverTo { get; set; } 39 | public decimal PremiumAmount { get; set; } 40 | } 41 | } -------------------------------------------------------------------------------- /NoCqrs/Init/DbInitializer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using NoCqrs.Domain; 4 | 5 | namespace NoCqrs.Init 6 | { 7 | public class DbInitializer 8 | { 9 | private readonly IDataStore dataStore; 10 | 11 | public DbInitializer(IDataStore dataStore) 12 | { 13 | this.dataStore = dataStore; 14 | } 15 | 16 | public void Seed() 17 | { 18 | dataStore.Products.Add(Products.StandardCarInsurance()); 19 | dataStore.CommitChanges(); 20 | 21 | var product = dataStore.Products.WithCode("STD_CAR_INSURANCE"); 22 | 23 | dataStore.Offers.Add(Offers.StandardOneYearOCOfferValidUntil(product, "OFF-001", SysTime.CurrentTime.AddDays(15))); 24 | dataStore.Offers.Add(Offers.StandardOneYearOCOfferValidUntil(product, "OFF-002", SysTime.CurrentTime.AddDays(-3))); 25 | dataStore.CommitChanges(); 26 | 27 | } 28 | } 29 | 30 | public static class ApplicationBuilderExtensions 31 | { 32 | public static void UseDbInitializer(this IApplicationBuilder app) 33 | { 34 | using (var scope = app.ApplicationServices.CreateScope()) 35 | { 36 | var dataStore = scope.ServiceProvider.GetService(); 37 | new DbInitializer(dataStore).Seed(); 38 | } 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /SeparateModels/Commands/ConfirmTerminationHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using System.Transactions; 4 | using MediatR; 5 | using SeparateModels.Domain; 6 | 7 | namespace SeparateModels.Commands 8 | { 9 | public class ConfirmTerminationHandler : IRequestHandler 10 | { 11 | private readonly IDataStore dataStore; 12 | private readonly IMediator mediator; 13 | 14 | public ConfirmTerminationHandler(IDataStore dataStore, IMediator mediator) 15 | { 16 | this.dataStore = dataStore; 17 | this.mediator = mediator; 18 | } 19 | 20 | 21 | public async Task Handle(ConfirmTerminationCommand command, CancellationToken cancellationToken) 22 | { 23 | var policy = await dataStore.Policies.WithNumber(command.PolicyNumber); 24 | policy.ConfirmChanges(command.VersionToConfirmNumber); 25 | 26 | await dataStore.CommitChanges(); 27 | 28 | await mediator.Publish(new PolicyTerminated(policy, 29 | policy.Versions.WithNumber(command.VersionToConfirmNumber))); 30 | 31 | return new ConfirmTerminationResult 32 | { 33 | PolicyNumber = policy.Number, 34 | VersionConfirmed = policy.Versions.LatestActive().VersionNumber 35 | }; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /SeparateModels/Commands/CancelLastAnnexHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using System.Transactions; 4 | using MediatR; 5 | using SeparateModels.Domain; 6 | using SeparateModels.Services; 7 | 8 | namespace SeparateModels.Commands 9 | { 10 | public class CancelLastAnnexHandler : IRequestHandler 11 | { 12 | private readonly IDataStore dataStore; 13 | private readonly IMediator mediator; 14 | 15 | public CancelLastAnnexHandler(IDataStore dataStore, IMediator mediator) 16 | { 17 | this.dataStore = dataStore; 18 | this.mediator = mediator; 19 | } 20 | 21 | public async Task Handle(CancelLastAnnexCommand command, CancellationToken cancellationToken) 22 | { 23 | var policy = await dataStore.Policies.WithNumber(command.PolicyNumber); 24 | var lastAnnex = policy.Versions.LatestActive(); 25 | policy.CancelLastAnnex(); 26 | 27 | await dataStore.CommitChanges(); 28 | 29 | await mediator.Publish(new PolicyAnnexCancelled(policy, lastAnnex, policy.Versions.LatestActive())); 30 | 31 | return new CancelLastAnnexResult 32 | { 33 | PolicyNumber = policy.Number, 34 | LastActiveVersionNumber = policy.Versions.LatestActive().VersionNumber 35 | }; 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Base/AggregateRoot.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using CqrsWithEs.Dragons; 4 | using MediatR; 5 | 6 | namespace CqrsWithEs.Domain.Base 7 | { 8 | public class AggregateRoot 9 | { 10 | private readonly List _changes = new List(); 11 | 12 | public Guid Id { get; protected set; } 13 | public int Version { get; internal set; } = -1; 14 | 15 | public IEnumerable GetUncommittedChanges() { 16 | return _changes; 17 | } 18 | 19 | public void MarkChangesAsCommitted() { 20 | _changes.Clear(); 21 | } 22 | 23 | public void LoadsFromHistory(IEnumerable history) { 24 | foreach (var e in history) 25 | { 26 | ApplyChange(e, false); 27 | Version += 1; 28 | } 29 | } 30 | 31 | protected void ApplyChange(Event @event) { 32 | ApplyChange(@event, true); 33 | } 34 | 35 | // push atomic aggregate changes to local history for further processing (EventStore.SaveEvents) 36 | private void ApplyChange(Event @event, bool isNew) { 37 | this.AsDynamic().Apply(@event); 38 | if (isNew) _changes.Add(@event); 39 | } 40 | } 41 | 42 | 43 | public class Event : INotification 44 | { 45 | public Guid Id; 46 | public int Version; 47 | public Event() { 48 | Id = Guid.NewGuid(); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /SeparateModels/Commands/ConfirmBuyAdditionalCoverHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using System.Transactions; 4 | using MediatR; 5 | using SeparateModels.Domain; 6 | 7 | namespace SeparateModels.Commands 8 | { 9 | public class ConfirmBuyAdditionalCoverHandler 10 | : IRequestHandler 11 | { 12 | private readonly IDataStore dataStore; 13 | private readonly IMediator mediator; 14 | 15 | public ConfirmBuyAdditionalCoverHandler(IDataStore dataStore, IMediator mediator) 16 | { 17 | this.dataStore = dataStore; 18 | this.mediator = mediator; 19 | } 20 | 21 | public async Task Handle(ConfirmBuyAdditionalCoverCommand command, CancellationToken cancellationToken) 22 | { 23 | var policy = await dataStore.Policies.WithNumber(command.PolicyNumber); 24 | policy.ConfirmChanges(command.VersionToConfirmNumber); 25 | 26 | await dataStore.CommitChanges(); 27 | 28 | await mediator.Publish(new PolicyAnnexed(policy, policy.Versions.WithNumber(command.VersionToConfirmNumber))); 29 | 30 | return new ConfirmBuyAdditionalCoverResult 31 | { 32 | PolicyNumber = policy.Number, 33 | VersionConfirmed = policy.Versions.LatestActive().VersionNumber 34 | }; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Policy/PolicyCover.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CqrsWithEs.Domain.Common; 3 | using CqrsWithEs.Domain.Offer; 4 | using NodaMoney; 5 | 6 | namespace CqrsWithEs.Domain.Policy 7 | { 8 | public class PolicyCover 9 | { 10 | public string CoverCode { get; } 11 | public ValidityPeriod CoverPeriod { get; } 12 | public UnitPrice Price { get; } 13 | public Money Amount { get; } 14 | 15 | public PolicyCover(string coverCode, ValidityPeriod coverPeriod, UnitPrice price) 16 | { 17 | CoverCode = coverCode; 18 | CoverPeriod = coverPeriod; 19 | Price = price; 20 | Amount = CalculateAmount(); 21 | } 22 | 23 | public static PolicyCover ForPrice(CoverPrice coverPrice, ValidityPeriod coverPeriod) 24 | { 25 | return new PolicyCover 26 | ( 27 | coverPrice.CoverCode, 28 | coverPeriod, 29 | new UnitPrice(coverPrice.Price, coverPrice.CoverPeriod) 30 | ); 31 | } 32 | 33 | public PolicyCover EndOn(DateTime lastDateOfCover) 34 | { 35 | return new PolicyCover 36 | ( 37 | CoverCode, 38 | CoverPeriod.EndOn(lastDateOfCover), 39 | Price 40 | ); 41 | } 42 | 43 | private Money CalculateAmount() 44 | { 45 | return decimal.Divide(CoverPeriod.Days, Price.PricePeriod.Days) * Price.Price; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /NoCqrs.Tests/TestData/OffersTestData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NoCqrs.Domain; 4 | using NodaMoney; 5 | using NodaTime; 6 | 7 | namespace NoCqrs.Tests 8 | { 9 | public class OffersTestData 10 | { 11 | public static Offer StandardOneYearOCOfferValidUntil(DateTime validityEnd) 12 | { 13 | var product = ProductsTestData.StandardCarInsurance(); 14 | return new Offer 15 | ( 16 | Guid.NewGuid(), 17 | "1", 18 | product, 19 | PersonsTestData.Kowalski(), 20 | PersonsTestData.Kowalski(), 21 | CarsTestData.OldFordFocus(), 22 | TimeSpan.FromDays(365), 23 | Money.Euro(500), 24 | validityEnd.AddDays(-30), 25 | new Dictionary() 26 | { 27 | {product.Covers.WithCode("OC"), Money.Euro(500) } 28 | } 29 | ); 30 | } 31 | 32 | public static Offer RejectedOfferValidUntil(DateTime validityEnd) 33 | { 34 | var offer = StandardOneYearOCOfferValidUntil(validityEnd); 35 | offer.Reject(); 36 | return offer; 37 | } 38 | 39 | public static Offer ConvertedOfferValidUntil(DateTime validityEnd) 40 | { 41 | var offer = StandardOneYearOCOfferValidUntil(validityEnd); 42 | offer.Convert(); 43 | return offer; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /CqrsWithEs/Commands/BuyAdditionalCoverHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using CqrsWithEs.Domain.Offer; 5 | using CqrsWithEs.Domain.Policy; 6 | using CqrsWithEs.Domain.Product; 7 | using MediatR; 8 | 9 | namespace CqrsWithEs.Commands 10 | { 11 | public class BuyAdditionalCoverHandler : IRequestHandler 12 | { 13 | private readonly IPolicyRepository policyRepository; 14 | 15 | public BuyAdditionalCoverHandler(IPolicyRepository policyRepository) 16 | { 17 | this.policyRepository = policyRepository; 18 | } 19 | 20 | public Task Handle(BuyAdditionalCoverCommand request, CancellationToken cancellationToken) 21 | { 22 | var policy = policyRepository.GetById(request.PolicyId); 23 | 24 | policy.ExtendCoverage 25 | ( 26 | request.EffectiveDateOfChange, 27 | new CoverPrice(request.NewCoverCode, request.NewCoverPrice, request.NewCoverPriceUnit) 28 | ); 29 | 30 | policyRepository.Save(policy, policy.Version); 31 | 32 | return Task.FromResult 33 | ( 34 | new BuyAdditionalCoverResult 35 | { 36 | PolicyNumber = policy.PolicyNumber, 37 | VersionWithAdditionalCoversNumber = policy.Versions.Last().VersionNumber 38 | } 39 | ); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /NoCqrs/Init/Offers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NoCqrs.Domain; 4 | using NodaMoney; 5 | 6 | namespace NoCqrs.Init 7 | { 8 | public class Offers 9 | { 10 | public static Offer StandardOneYearOCOfferValidUntil(Product product, string number, DateTime validityEnd) 11 | { 12 | return new Offer 13 | ( 14 | Guid.NewGuid(), 15 | number, 16 | product, 17 | Persons.Kowalski(), 18 | Persons.Kowalski(), 19 | Cars.OldFordFocus(), 20 | TimeSpan.FromDays(365), 21 | Money.Euro(500), 22 | validityEnd.AddDays(-30), 23 | new Dictionary() 24 | { 25 | {product.Covers.WithCode("OC"), Money.Euro(500) } 26 | } 27 | ); 28 | } 29 | 30 | public static Offer RejectedOfferValidUntil(Product product, string number, DateTime validityEnd) 31 | { 32 | var offer = StandardOneYearOCOfferValidUntil(product, number, validityEnd); 33 | offer.Reject(); 34 | return offer; 35 | } 36 | 37 | public static Offer ConvertedOfferValidUntil(Product product, string number ,DateTime validityEnd) 38 | { 39 | var offer = StandardOneYearOCOfferValidUntil(product, number, validityEnd); 40 | offer.Convert(); 41 | return offer; 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /NoCqrs.Tests/PolicyCancelAnnexUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NoCqrs.Domain; 4 | using NodaMoney; 5 | using Xunit; 6 | using static Xunit.Assert; 7 | 8 | namespace NoCqrs.Tests 9 | { 10 | public class PolicyCancelAnnexUnitTests 11 | { 12 | [Fact] 13 | public void CanUndoTermination() 14 | { 15 | var policyStartedAt = new DateTime(2019, 1, 1); 16 | var policyTerminatedAt = new DateTime(2019,7,1); 17 | var policy = PolicyTestData.StandardOneYearPolicyTerminated(policyStartedAt, policyTerminatedAt); 18 | 19 | policy.CancelLastAnnex(); 20 | 21 | Equal(2, policy.Versions.Count()); 22 | Equal(Money.Euro(500), policy.Versions.WithNumber(1).TotalPremium); 23 | Equal(PolicyVersionStatus.Active, policy.Versions.WithNumber(1).VersionStatus); 24 | Equal(PolicyStatus.Active, policy.Versions.WithNumber(1).PolicyStatus); 25 | 26 | Equal(PolicyStatus.Terminated, policy.Versions.WithNumber(2).PolicyStatus); 27 | Equal(PolicyVersionStatus.Cancelled, policy.Versions.WithNumber(2).VersionStatus); 28 | Equal(policyTerminatedAt , policy.Versions.WithNumber(2).VersionValidityPeriod.ValidFrom); 29 | Equal(policyTerminatedAt.AddDays(-1), policy.Versions.WithNumber(2).CoverPeriod.ValidTo); 30 | Equal(new DateTime(2019,1,1), policy.Versions.WithNumber(2).CoverPeriod.ValidFrom); 31 | Equal(Money.Euro(246.58), policy.Versions.WithNumber(2).TotalPremium); 32 | 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /SeparateModels/Init/Offers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NodaMoney; 4 | using SeparateModels.Domain; 5 | 6 | namespace SeparateModels.Init 7 | { 8 | public class Offers 9 | { 10 | public static Offer StandardOneYearOCOfferValidUntil(Product product, string number, DateTime validityEnd) 11 | { 12 | var coverPrices = new Dictionary() 13 | { 14 | {product.Covers.WithCode("OC"), Money.Euro(500)} 15 | }; 16 | 17 | return new Offer 18 | ( 19 | Guid.NewGuid(), 20 | number, 21 | product, 22 | Persons.Kowalski(), 23 | Persons.Kowalski(), 24 | Cars.OldFordFocus(), 25 | TimeSpan.FromDays(365), 26 | Money.Euro(500), 27 | validityEnd.AddDays(-30), 28 | coverPrices 29 | ); 30 | } 31 | 32 | public static Offer RejectedOfferValidUntil(Product product, string number, DateTime validityEnd) 33 | { 34 | var offer = StandardOneYearOCOfferValidUntil(product, number, validityEnd); 35 | offer.Reject(); 36 | return offer; 37 | } 38 | 39 | public static Offer ConvertedOfferValidUntil(Product product, string number ,DateTime validityEnd) 40 | { 41 | var offer = StandardOneYearOCOfferValidUntil(product, number, validityEnd); 42 | offer.Convert(); 43 | return offer; 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /CqrsWithEs/Scripts/create_readmodel_tables.sql: -------------------------------------------------------------------------------- 1 | create table policy_info_view 2 | ( 3 | policy_id uuid not null primary key, 4 | policy_number character varying(50) not null, 5 | cover_from timestamp with time zone not null, 6 | cover_to timestamp with time zone not null, 7 | vehicle character varying(150) not null, 8 | policy_holder character varying(50) not null, 9 | total_premium numeric(19,2) not null 10 | ); 11 | 12 | 13 | create table policy_version_view 14 | ( 15 | policy_version_id uuid not null primary key, 16 | policy_id uuid not null, 17 | policy_number character varying(50) not null, 18 | product_code character varying(50) not null, 19 | version_number int not null, 20 | version_status character varying(50) not null, 21 | policy_status character varying(50) not null, 22 | policy_holder character varying(250) not null, 23 | insured character varying(250) not null, 24 | car character varying(250) not null, 25 | cover_from timestamp with time zone not null, 26 | cover_to timestamp with time zone not null, 27 | version_from timestamp with time zone not null, 28 | version_to timestamp with time zone not null, 29 | total_premium numeric(19,2) not null 30 | ); 31 | 32 | create table policy_version_cover 33 | ( 34 | policy_version_cover_id uuid not null primary key, 35 | policy_version_id uuid not null, 36 | code character varying(50) not null, 37 | cover_from timestamp with time zone not null, 38 | cover_to timestamp with time zone not null, 39 | premium_amount numeric(19,2) not null, 40 | constraint policy_version_fk foreign key (policy_version_id) references policy_version_view(policy_version_id) 41 | ); -------------------------------------------------------------------------------- /SeparateModels/DbScripts/create_readmodel_tables.sql: -------------------------------------------------------------------------------- 1 | create table policy_info_view 2 | ( 3 | policy_id uuid not null primary key, 4 | policy_number character varying(50) not null, 5 | cover_from timestamp with time zone not null, 6 | cover_to timestamp with time zone not null, 7 | vehicle character varying(150) not null, 8 | policy_holder character varying(50) not null, 9 | total_premium numeric(19,2) not null 10 | ); 11 | 12 | 13 | create table policy_version_view 14 | ( 15 | policy_version_id uuid not null primary key, 16 | policy_id uuid not null, 17 | policy_number character varying(50) not null, 18 | product_code character varying(50) not null, 19 | version_number int not null, 20 | version_status character varying(50) not null, 21 | policy_status character varying(50) not null, 22 | policy_holder character varying(250) not null, 23 | insured character varying(250) not null, 24 | car character varying(250) not null, 25 | cover_from timestamp with time zone not null, 26 | cover_to timestamp with time zone not null, 27 | version_from timestamp with time zone not null, 28 | version_to timestamp with time zone not null, 29 | total_premium numeric(19,2) not null 30 | ); 31 | 32 | create table policy_version_cover 33 | ( 34 | policy_version_cover_id uuid not null primary key, 35 | policy_version_id uuid not null, 36 | code character varying(50) not null, 37 | cover_from timestamp with time zone not null, 38 | cover_to timestamp with time zone not null, 39 | premium_amount numeric(19,2) not null, 40 | constraint policy_version_fk foreign key (policy_version_id) references policy_version_view(policy_version_id) 41 | ); -------------------------------------------------------------------------------- /CqrsWithEs.Tests/TestData/OffersTestData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using CqrsWithEs.Domain; 4 | using CqrsWithEs.Domain.Offer; 5 | using CqrsWithEs.Domain.Product; 6 | using NodaMoney; 7 | using NodaTime; 8 | 9 | namespace CqrsWithEs.Tests 10 | { 11 | public class OffersTestData 12 | { 13 | public static Offer StandardOneYearOCOfferValidUntil(DateTime validityEnd) 14 | { 15 | var product = ProductsTestData.StandardCarInsurance(); 16 | return new Offer 17 | ( 18 | Guid.NewGuid(), 19 | "1", 20 | product, 21 | PersonsTestData.Kowalski(), 22 | PersonsTestData.Kowalski(), 23 | CarsTestData.OldFordFocus(), 24 | TimeSpan.FromDays(365), 25 | Money.Euro(500), 26 | validityEnd.AddDays(-30), 27 | new Dictionary() 28 | { 29 | {product.CoverWithCode("OC"), Money.Euro(500) } 30 | } 31 | ); 32 | } 33 | 34 | public static Offer RejectedOfferValidUntil(DateTime validityEnd) 35 | { 36 | var offer = StandardOneYearOCOfferValidUntil(validityEnd); 37 | offer.Reject(); 38 | return offer; 39 | } 40 | 41 | public static Offer ConvertedOfferValidUntil(DateTime validityEnd) 42 | { 43 | var offer = StandardOneYearOCOfferValidUntil(validityEnd); 44 | offer.Convert(); 45 | return offer; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /SeparateModels/DataAccess/Marten/MartenInstaller.cs: -------------------------------------------------------------------------------- 1 | using Marten; 2 | using Marten.Services; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using SeparateModels.Domain; 5 | 6 | namespace SeparateModels.DataAccess.Marten 7 | { 8 | public static class MartenInstaller 9 | { 10 | public static void AddMarten(this IServiceCollection services, string cnnString) 11 | { 12 | services.AddSingleton(CreateDocumentStore(cnnString)); 13 | 14 | services.AddScoped(); 15 | } 16 | 17 | private static IDocumentStore CreateDocumentStore(string cn) 18 | { 19 | return DocumentStore.For(_ => 20 | { 21 | _.Connection(cn); 22 | _.Serializer(CustomizeJsonSerializer()); 23 | 24 | _.Schema.For().DocumentAlias("policy_agg").Duplicate(t => t.Number,pgType: "varchar(50)", configure: idx => idx.IsUnique = true); 25 | _.Schema.For().DocumentAlias("offer_agg").Duplicate(t => t.Number,pgType: "varchar(50)", configure: idx => idx.IsUnique = true); 26 | _.Schema.For().DocumentAlias("product_agg").Duplicate(t => t.Code,pgType: "varchar(50)", configure: idx => idx.IsUnique = true); 27 | }); 28 | } 29 | 30 | private static JsonNetSerializer CustomizeJsonSerializer() 31 | { 32 | var serializer = new JsonNetSerializer(); 33 | 34 | serializer.Customize(_ => 35 | { 36 | _.ContractResolver = new ProtectedSettersContractResolver(); 37 | }); 38 | 39 | return serializer; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /SeparateModels/Commands/BuyAdditionalCoverHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using System.Transactions; 5 | using MediatR; 6 | using Microsoft.EntityFrameworkCore.Storage; 7 | using NodaMoney; 8 | using SeparateModels.Domain; 9 | using SeparateModels.Services; 10 | 11 | namespace SeparateModels.Commands 12 | { 13 | public class BuyAdditionalCoverHandler : IRequestHandler 14 | { 15 | private IDataStore dataStore; 16 | 17 | public BuyAdditionalCoverHandler(IDataStore dataStore) 18 | { 19 | this.dataStore = dataStore; 20 | } 21 | 22 | public async Task Handle(BuyAdditionalCoverCommand request, CancellationToken cancellationToken) 23 | { 24 | var policy = await dataStore.Policies.WithNumber(request.PolicyNumber); 25 | var product = await dataStore.Products.WithCode(policy.ProductCode); 26 | var newCover = product.Covers.WithCode(request.NewCoverCode); 27 | 28 | policy.ExtendCoverage 29 | ( 30 | request.EffectiveDateOfChange, 31 | new CoverPrice(newCover, Money.Euro(request.NewCoverPrice), request.NewCoverPriceUnit) 32 | ); 33 | var newPolicyVersion = policy.Versions.Last(); 34 | 35 | await dataStore.CommitChanges(); 36 | 37 | 38 | return new BuyAdditionalCoverResult 39 | { 40 | PolicyNumber = policy.Number, 41 | VersionWithAdditionalCoversNumber = newPolicyVersion.VersionNumber 42 | }; 43 | } 44 | } 45 | } -------------------------------------------------------------------------------- /NoCqrs/Domain/ValidityPeriod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace NoCqrs.Domain 5 | { 6 | public class ValidityPeriod : ValueObject, ICloneable 7 | { 8 | public virtual DateTime ValidFrom { get; private set; } 9 | public virtual DateTime ValidTo { get; private set; } 10 | 11 | public ValidityPeriod(DateTime validFrom, DateTime validTo) 12 | { 13 | ValidFrom = validFrom; 14 | ValidTo = validTo; 15 | } 16 | 17 | protected ValidityPeriod() 18 | { 19 | } //NH required 20 | 21 | public static ValidityPeriod Between(DateTime validFrom, DateTime validTo) 22 | => new ValidityPeriod(validFrom, validTo); 23 | 24 | public ValidityPeriod Clone() 25 | { 26 | return new ValidityPeriod(ValidFrom, ValidTo); 27 | } 28 | 29 | public bool Contains(DateTime theDate) 30 | { 31 | if (theDate > ValidTo) 32 | return false; 33 | 34 | if (theDate < ValidFrom) 35 | return false; 36 | 37 | return true; 38 | } 39 | 40 | public ValidityPeriod EndOn(DateTime endDate) 41 | { 42 | return new ValidityPeriod(ValidFrom, endDate); 43 | } 44 | 45 | public int Days => ValidTo.Subtract(ValidFrom).Days; 46 | 47 | object ICloneable.Clone() 48 | { 49 | return Clone(); 50 | } 51 | 52 | protected override IEnumerable GetAttributesToIncludeInEqualityCheck() 53 | { 54 | return new List { ValidFrom, ValidTo }; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /SeparateModels/Domain/ValidityPeriod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace SeparateModels.Domain 5 | { 6 | public class ValidityPeriod : ValueObject, ICloneable 7 | { 8 | public virtual DateTime ValidFrom { get; private set; } 9 | public virtual DateTime ValidTo { get; private set; } 10 | 11 | public ValidityPeriod(DateTime validFrom, DateTime validTo) 12 | { 13 | ValidFrom = validFrom; 14 | ValidTo = validTo; 15 | } 16 | 17 | protected ValidityPeriod() 18 | { 19 | } //NH required 20 | 21 | public static ValidityPeriod Between(DateTime validFrom, DateTime validTo) 22 | => new ValidityPeriod(validFrom, validTo); 23 | 24 | public ValidityPeriod Clone() 25 | { 26 | return new ValidityPeriod(ValidFrom, ValidTo); 27 | } 28 | 29 | public bool Contains(DateTime theDate) 30 | { 31 | if (theDate > ValidTo) 32 | return false; 33 | 34 | if (theDate < ValidFrom) 35 | return false; 36 | 37 | return true; 38 | } 39 | 40 | public ValidityPeriod EndOn(DateTime endDate) 41 | { 42 | return new ValidityPeriod(ValidFrom, endDate); 43 | } 44 | 45 | public int Days => ValidTo.Subtract(ValidFrom).Days; 46 | 47 | object ICloneable.Clone() 48 | { 49 | return Clone(); 50 | } 51 | 52 | protected override IEnumerable GetAttributesToIncludeInEqualityCheck() 53 | { 54 | return new List { ValidFrom, ValidTo }; 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /NoCqrs/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.HttpsPolicy; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.Extensions.Configuration; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Logging; 12 | using Microsoft.Extensions.Options; 13 | using NoCqrs.Init; 14 | using NoCqrs.Installers; 15 | using NoCqrs.Services; 16 | 17 | namespace NoCqrs 18 | { 19 | public class Startup 20 | { 21 | public Startup(IConfiguration configuration) 22 | { 23 | Configuration = configuration; 24 | } 25 | 26 | public IConfiguration Configuration { get; } 27 | 28 | // This method gets called by the runtime. Use this method to add services to the container. 29 | public void ConfigureServices(IServiceCollection services) 30 | { 31 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 32 | services.AddEF(Configuration); 33 | services.AddScoped(); 34 | } 35 | 36 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 37 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 38 | { 39 | if (env.IsDevelopment()) 40 | { 41 | app.UseDeveloperExceptionPage(); 42 | } 43 | else 44 | { 45 | app.UseHsts(); 46 | } 47 | 48 | app.UseHttpsRedirection(); 49 | app.UseMvc(); 50 | app.UseDbInitializer(); 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Common/ValidityPeriod.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using CqrsWithEs.Domain.Base; 4 | 5 | namespace CqrsWithEs.Domain.Common 6 | { 7 | public class ValidityPeriod : ValueObject, ICloneable 8 | { 9 | public virtual DateTime ValidFrom { get; private set; } 10 | public virtual DateTime ValidTo { get; private set; } 11 | 12 | public ValidityPeriod(DateTime validFrom, DateTime validTo) 13 | { 14 | ValidFrom = validFrom; 15 | ValidTo = validTo; 16 | } 17 | 18 | protected ValidityPeriod() 19 | { 20 | } //NH required 21 | 22 | public static ValidityPeriod Between(DateTime validFrom, DateTime validTo) 23 | => new ValidityPeriod(validFrom, validTo); 24 | 25 | public ValidityPeriod Clone() 26 | { 27 | return new ValidityPeriod(ValidFrom, ValidTo); 28 | } 29 | 30 | public bool Contains(DateTime theDate) 31 | { 32 | if (theDate > ValidTo) 33 | return false; 34 | 35 | if (theDate < ValidFrom) 36 | return false; 37 | 38 | return true; 39 | } 40 | 41 | public ValidityPeriod EndOn(DateTime endDate) 42 | { 43 | return new ValidityPeriod(ValidFrom, endDate); 44 | } 45 | 46 | public int Days => ValidTo.Subtract(ValidFrom).Days; 47 | 48 | object ICloneable.Clone() 49 | { 50 | return Clone(); 51 | } 52 | 53 | protected override IEnumerable GetAttributesToIncludeInEqualityCheck() 54 | { 55 | return new List { ValidFrom, ValidTo }; 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /CqrsWithEs/EventHandlers/PolicyInfoSynchronizer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using CqrsWithEs.Domain.Policy.Events; 6 | using CqrsWithEs.ReadModel; 7 | using MediatR; 8 | 9 | namespace CqrsWithEs.EventHandlers 10 | { 11 | public class PolicyInfoSynchronizer : 12 | INotificationHandler, 13 | INotificationHandler, 14 | INotificationHandler 15 | { 16 | private readonly PolicyInfoDao policyInfoDao; 17 | 18 | public PolicyInfoSynchronizer(PolicyInfoDao policyInfoDao) 19 | { 20 | this.policyInfoDao = policyInfoDao; 21 | } 22 | 23 | public Task Handle(InitialPolicyVersionCreated @event, CancellationToken cancellationToken) 24 | { 25 | policyInfoDao.CreatePolicyInfo 26 | ( 27 | Guid.NewGuid(), 28 | @event.PolicyNumber, 29 | @event.CoverFrom, 30 | @event.CoverTo, 31 | @event.PolicyHolder.FirstName, @event.PolicyHolder.LastName, 32 | @event.Car.PlateNumber, @event.Car.Make, 33 | @event.Covers.Select(c=>c.Amount.Amount).Sum() 34 | ); 35 | 36 | return Task.CompletedTask; 37 | } 38 | 39 | public Task Handle(CoverageExtendedPolicyVersionConfirmed @event, CancellationToken cancellationToken) 40 | { 41 | throw new NotImplementedException(); 42 | } 43 | 44 | public Task Handle(TerminalPolicyVersionConfirmed notification, CancellationToken cancellationToken) 45 | { 46 | throw new NotImplementedException(); 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /SeparateModels/Startup.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using SeparateModels.DataAccess.Marten; 8 | using SeparateModels.Init; 9 | using SeparateModels.Installers; 10 | using SeparateModels.ReadModels; 11 | using SeparateModels.Services; 12 | 13 | namespace SeparateModels 14 | { 15 | public class Startup 16 | { 17 | public Startup(IConfiguration configuration) 18 | { 19 | Configuration = configuration; 20 | } 21 | 22 | public IConfiguration Configuration { get; } 23 | 24 | // This method gets called by the runtime. Use this method to add services to the container. 25 | public void ConfigureServices(IServiceCollection services) 26 | { 27 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 28 | services.AddMediatR(); 29 | services.AddMarten(Configuration.GetConnectionString("DefaultConnection")); 30 | services.AddReadModels(Configuration.GetConnectionString("DefaultConnection")); 31 | } 32 | 33 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 34 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 35 | { 36 | if (env.IsDevelopment()) 37 | { 38 | app.UseDeveloperExceptionPage(); 39 | } 40 | else 41 | { 42 | app.UseHsts(); 43 | } 44 | 45 | app.UseHttpsRedirection(); 46 | app.UseMvc(); 47 | app.UseDbInitializer(); 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Policy/Events/PolicyEventsData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NodaMoney; 3 | 4 | namespace CqrsWithEs.Domain.Policy.Events 5 | { 6 | 7 | public class PersonData 8 | { 9 | public string FirstName { get; private set; } 10 | public string LastName { get; private set; } 11 | public string TaxId { get; private set; } 12 | 13 | public PersonData(string firstName, string lastName, string taxId) 14 | { 15 | FirstName = firstName; 16 | LastName = lastName; 17 | TaxId = taxId; 18 | } 19 | } 20 | 21 | public class CarData 22 | { 23 | public string Make { get; private set; } 24 | public string PlateNumber { get; private set; } 25 | public int ProductionYear { get; private set; } 26 | 27 | public CarData(string make, string plateNumber, int productionYear) 28 | { 29 | Make = make; 30 | PlateNumber = plateNumber; 31 | ProductionYear = productionYear; 32 | } 33 | } 34 | 35 | public class PolicyCoverData 36 | { 37 | public string Code { get; private set; } 38 | public DateTime CoverFrom { get; private set; } 39 | public DateTime CoverTo { get; private set; } 40 | public Money Amount { get; private set; } 41 | public Money Price { get; private set; } 42 | public TimeSpan PriceUnit { get; private set; } 43 | 44 | public PolicyCoverData(string code, DateTime coverFrom, DateTime coverTo, Money amount, Money price, TimeSpan priceUnit) 45 | { 46 | Code = code; 47 | CoverFrom = coverFrom; 48 | CoverTo = coverTo; 49 | Amount = amount; 50 | Price = price; 51 | PriceUnit = priceUnit; 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /SeparateModels/Domain/PolicyEvents.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace SeparateModels.Domain 4 | { 5 | public class PolicyCreated : INotification 6 | { 7 | public Policy NewPolicy { get; } 8 | 9 | public PolicyCreated(Policy newPolicy) 10 | { 11 | NewPolicy = newPolicy; 12 | } 13 | } 14 | 15 | public class PolicyAnnexed : INotification 16 | { 17 | public Policy AnnexedPolicy { get; } 18 | public PolicyVersion AnnexVersion { get; } 19 | 20 | public PolicyAnnexed(Policy annexedPolicy, PolicyVersion annexVersion) 21 | { 22 | AnnexedPolicy = annexedPolicy; 23 | AnnexVersion = annexVersion; 24 | } 25 | } 26 | 27 | public class PolicyTerminated : INotification 28 | { 29 | public Policy TerminatedPolicy { get; } 30 | public PolicyVersion TerminatedVersion { get; } 31 | 32 | public PolicyTerminated(Policy terminatedPolicy, PolicyVersion terminatedVersion) 33 | { 34 | TerminatedPolicy = terminatedPolicy; 35 | TerminatedVersion = terminatedVersion; 36 | } 37 | } 38 | 39 | public class PolicyAnnexCancelled : INotification 40 | { 41 | public Policy Policy { get; } 42 | public PolicyVersion CancelledAnnexVersion { get; } 43 | public PolicyVersion CurrentVersionAfterAnnexCancellation { get; } 44 | 45 | public PolicyAnnexCancelled( 46 | Policy policy, 47 | PolicyVersion cancelledAnnexVersion, 48 | PolicyVersion currentVersionAfterAnnexCancellation) 49 | { 50 | Policy = policy; 51 | CancelledAnnexVersion = cancelledAnnexVersion; 52 | CurrentVersionAfterAnnexCancellation = currentVersionAfterAnnexCancellation; 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /CqrsWithEs.Tests/TestData/PolicyTestData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using CqrsWithEs.Domain; 4 | using CqrsWithEs.Domain.Base; 5 | using CqrsWithEs.Domain.Policy; 6 | using CqrsWithEs.Domain.Policy.Events; 7 | using NodaMoney; 8 | 9 | namespace CqrsWithEs.Tests 10 | { 11 | public static class PolicyTestData 12 | { 13 | public static Policy StandardOneYearPolicy(DateTime policyStartDate) 14 | { 15 | var product = ProductsTestData.StandardCarInsurance(); 16 | 17 | var events = new List 18 | { 19 | new InitialPolicyVersionCreated 20 | ( 21 | "POL01", 22 | product.Code, 23 | policyStartDate, 24 | policyStartDate.AddDays(365), 25 | policyStartDate.AddDays(-1), 26 | new PersonData("A", "B", "C"), 27 | new CarData("A","B", 2018), 28 | new List 29 | { 30 | new PolicyCoverData("OC",policyStartDate,policyStartDate.AddDays(365),Money.Euro(500),Money.Euro(500),TimeSpan.FromDays(365)) 31 | } 32 | ) 33 | }; 34 | 35 | var policy = new Policy(Guid.NewGuid(), events); 36 | policy.MarkChangesAsCommitted(); 37 | return policy; 38 | } 39 | 40 | /*public static Policy StandardOneYearPolicyTerminated(DateTime policyStartDate, DateTime policyTerminationDate) 41 | { 42 | var policy = StandardOneYearPolicy(policyStartDate); 43 | policy.TerminatePolicy(policyTerminationDate); 44 | policy.ConfirmChanges(2); 45 | 46 | return policy; 47 | }*/ 48 | } 49 | } -------------------------------------------------------------------------------- /CqrsWithEs/Controllers/PoliciesController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using CqrsWithEs.Commands; 3 | using MediatR; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | namespace CqrsWithEs.Controllers 7 | { 8 | [Route("api/[controller]")] 9 | [ApiController] 10 | public class PoliciesController : ControllerBase 11 | { 12 | private readonly IMediator mediator; 13 | 14 | public PoliciesController(IMediator mediator) 15 | { 16 | this.mediator = mediator; 17 | } 18 | 19 | [HttpPost] 20 | public async Task CreatePolicy([FromBody] CreatePolicyCommand cmd) 21 | { 22 | var result = await mediator.Send(cmd); 23 | return Ok(result); 24 | } 25 | 26 | [HttpPost("BuyAdditionalCover")] 27 | public async Task BuyAdditionalCover([FromBody] BuyAdditionalCoverCommand cmd) 28 | { 29 | var result = await mediator.Send(cmd); 30 | return Ok(result); 31 | } 32 | 33 | [HttpPost("ConfirmBuyAdditionalCover")] 34 | public async Task Post([FromBody] ConfirmBuyAdditionalCoverCommand cmd) 35 | { 36 | var result = await mediator.Send(cmd); 37 | return Ok(result); 38 | } 39 | 40 | [HttpPost("Terminate")] 41 | public async Task Terminate([FromBody] TerminatePolicyCommand cmd) 42 | { 43 | var result = await mediator.Send(cmd); 44 | return Ok(result); 45 | } 46 | 47 | [HttpPost("ConfirmTermination")] 48 | public async Task ConfirmTermination([FromBody] ConfirmTerminationCommand cmd) 49 | { 50 | var result = await mediator.Send(cmd); 51 | return Ok(result); 52 | } 53 | 54 | } 55 | } -------------------------------------------------------------------------------- /NoCqrs.Tests/PolicyTerminationUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NoCqrs.Domain; 4 | using NodaMoney; 5 | using Xunit; 6 | using static Xunit.Assert; 7 | 8 | namespace NoCqrs.Tests 9 | { 10 | public class PolicyTerminationUnitTests 11 | { 12 | [Fact] 13 | public void CanTerminatePolicyInTheMiddle() 14 | { 15 | var policy = PolicyTestData.StandardOneYearPolicy(new DateTime(2019, 1, 1)); 16 | var terminationDate = new DateTime(2019, 7, 1); 17 | policy.TerminatePolicy(terminationDate); 18 | 19 | Equal(2, policy.Versions.Count()); 20 | Equal(Money.Euro(500), policy.Versions.WithNumber(1).TotalPremium); 21 | Equal(PolicyVersionStatus.Active, policy.Versions.WithNumber(1).VersionStatus); 22 | 23 | Equal(PolicyStatus.Terminated, policy.Versions.WithNumber(2).PolicyStatus); 24 | Equal(PolicyVersionStatus.Draft, policy.Versions.WithNumber(2).VersionStatus); 25 | Equal(terminationDate, policy.Versions.WithNumber(2).VersionValidityPeriod.ValidFrom); 26 | Equal(terminationDate.AddDays(-1), policy.Versions.WithNumber(2).CoverPeriod.ValidTo); 27 | Equal(new DateTime(2019,1,1), policy.Versions.WithNumber(2).CoverPeriod.ValidFrom); 28 | Equal(Money.Euro(246.58), policy.Versions.WithNumber(2).TotalPremium); 29 | } 30 | 31 | [Fact] 32 | public void CannotTerminatePolicyAfterCoverEnds() 33 | { 34 | var policy = PolicyTestData.StandardOneYearPolicy(new DateTime(2019, 1, 1)); 35 | var terminationDate = new DateTime(2020, 1, 2); 36 | var ex = Assert.Throws(() => policy.TerminatePolicy(terminationDate)); 37 | Equal("No active version at given date", ex.Message); 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Policy/Events/CoverageExtendedPolicyVersionCreated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CqrsWithEs.Domain.Base; 3 | using CqrsWithEs.Domain.Common; 4 | 5 | namespace CqrsWithEs.Domain.Policy.Events 6 | { 7 | public class CoverageExtendedPolicyVersionCreated : Event 8 | { 9 | public int VersionNumber { get; private set; } 10 | public int BaseVersionNumber { get; private set; } 11 | public DateTime VersionFrom { get; private set; } 12 | public DateTime VersionTo { get; private set; } 13 | public PolicyCoverData NewCover { get; private set; } 14 | 15 | public CoverageExtendedPolicyVersionCreated 16 | ( 17 | int versionNumber, 18 | int baseVersionNumber, 19 | ValidityPeriod versionPeriod, 20 | PolicyCover newCover 21 | ) 22 | { 23 | VersionNumber = versionNumber; 24 | BaseVersionNumber = baseVersionNumber; 25 | VersionFrom = versionPeriod.ValidFrom; 26 | VersionTo = versionPeriod.ValidTo; 27 | NewCover = new PolicyCoverData 28 | ( 29 | newCover.CoverCode, 30 | newCover.CoverPeriod.ValidFrom, 31 | newCover.CoverPeriod.ValidTo, 32 | newCover.Amount, 33 | newCover.Price.Price, 34 | newCover.Price.PricePeriod 35 | ); 36 | } 37 | 38 | public CoverageExtendedPolicyVersionCreated 39 | ( 40 | int versionNumber, 41 | int baseVersionNumber, 42 | ValidityPeriod versionPeriod, 43 | PolicyCoverData newCover 44 | ) 45 | { 46 | VersionNumber = versionNumber; 47 | BaseVersionNumber = baseVersionNumber; 48 | VersionFrom = versionPeriod.ValidFrom; 49 | VersionTo = versionPeriod.ValidTo; 50 | NewCover = newCover; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /SeparateModels/Init/DbInitializer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using SeparateModels.Domain; 6 | 7 | namespace SeparateModels.Init 8 | { 9 | public class DbInitializer 10 | { 11 | private readonly IDataStore dataStore; 12 | 13 | public DbInitializer(IDataStore dataStore) 14 | { 15 | this.dataStore = dataStore; 16 | } 17 | 18 | public async Task Seed() 19 | { 20 | var product = await dataStore.Products.WithCode("STD_CAR_INSURANCE"); 21 | if (product == null) 22 | { 23 | product = Products.StandardCarInsurance(); 24 | dataStore.Products.Add(product); 25 | await dataStore.CommitChanges(); 26 | } 27 | 28 | 29 | var offer = await dataStore.Offers.WithNumber("OFF-001"); 30 | if (offer == null) 31 | { 32 | dataStore.Offers.Add( 33 | Offers.StandardOneYearOCOfferValidUntil(product, "OFF-001", SysTime.CurrentTime.AddDays(15))); 34 | } 35 | 36 | 37 | var offer2 = await dataStore.Offers.WithNumber("OFF-002"); 38 | if (offer2 == null) 39 | { 40 | dataStore.Offers.Add( 41 | Offers.StandardOneYearOCOfferValidUntil(product, "OFF-002", SysTime.CurrentTime.AddDays(-3))); 42 | } 43 | 44 | await dataStore.CommitChanges(); 45 | 46 | } 47 | } 48 | 49 | public static class ApplicationBuilderExtensions 50 | { 51 | public static async Task UseDbInitializer(this IApplicationBuilder app) 52 | { 53 | using (var scope = app.ApplicationServices.CreateScope()) 54 | { 55 | var dataStore = scope.ServiceProvider.GetService(); 56 | await new DbInitializer(dataStore).Seed(); 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /CqrsWithEs/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using CqrsWithEs.DataAccess; 6 | using CqrsWithEs.Init; 7 | using CqrsWithEs.ReadModel; 8 | using MediatR; 9 | using Microsoft.AspNetCore.Builder; 10 | using Microsoft.AspNetCore.Hosting; 11 | using Microsoft.AspNetCore.HttpsPolicy; 12 | using Microsoft.AspNetCore.Mvc; 13 | using Microsoft.Extensions.Configuration; 14 | using Microsoft.Extensions.DependencyInjection; 15 | using Microsoft.Extensions.Logging; 16 | using Microsoft.Extensions.Options; 17 | 18 | namespace CqrsWithEs 19 | { 20 | public class Startup 21 | { 22 | public Startup(IConfiguration configuration) 23 | { 24 | Configuration = configuration; 25 | } 26 | 27 | public IConfiguration Configuration { get; } 28 | 29 | // This method gets called by the runtime. Use this method to add services to the container. 30 | public void ConfigureServices(IServiceCollection services) 31 | { 32 | services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); 33 | services.AddMediatR(); 34 | services.AddDataAccess(); 35 | services.AddDataInitializer(); 36 | services.AddReadModels(Configuration.GetConnectionString("DefaultConnection")); 37 | } 38 | 39 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 40 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 41 | { 42 | if (env.IsDevelopment()) 43 | { 44 | app.UseDeveloperExceptionPage(); 45 | } 46 | else 47 | { 48 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 49 | app.UseHsts(); 50 | } 51 | 52 | app.UseDataInitializer(); 53 | app.UseHttpsRedirection(); 54 | app.UseMvc(); 55 | 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CQRS and Event Sourcing Intro for Developers 2 | 3 | We live in a world of dynamically changing technologies. New ways of architecturing our solutions, new frameworks and libraries seem to appear on almost daily basis. 4 | 5 | 6 | **But good software engineering is not about fancy frameworks and solutions aggressively promoted by their vendors.** It is not about doing something because Netflix or Google did it. It is about taking well-thought-out decisions based on facts and knowledge. That’s why it is important to be familiar basic architectural concepts like CQRS. It is one of the tools we use in our software house every day. We mentioned CQRS in the article which is part of the series about [Microservices on .NET Core](https://altkomsoftware.pl/en/blog/building-microservices-on-net-core-1/), but it was presented from technical perspective and here we want to focus on basics concepts explanation with visualisation and examples. 7 | 8 | 9 | [Check our article!](https://altkomsoftware.pl/en/blog/cqrs-event-sourcing/) 10 | 11 | ## No CQRS 12 | 13 |

14 | No CQRS 15 |

16 | 17 | ## Separate Commands and Queries 18 | 19 |

20 | Separate Commands and Queries 21 |

22 | 23 | ## Separate Models Commands and Queries 24 | 25 |

26 | Separate Models Commands and Queries 27 |

28 | 29 | ## Separate Storage Engines 30 | 31 |

32 | Separate Storage Engines 33 |

34 | 35 | ## Event Sourcing 36 | 37 |

38 | Event Sourcing 39 |

40 | -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Policy/Events/TerminalPolicyVersionCreated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CqrsWithEs.Domain.Base; 5 | 6 | namespace CqrsWithEs.Domain.Policy.Events 7 | { 8 | public class TerminalPolicyVersionCreated : Event 9 | { 10 | public int VersionNumber { get; private set; } 11 | public int BaseVersionNumber { get; private set; } 12 | public DateTime VersionFrom { get; private set; } 13 | public DateTime VersionTo { get; private set; } 14 | public DateTime CoverFrom { get; private set; } 15 | public DateTime CoverTo { get; private set; } 16 | public List Covers { get; private set; } 17 | 18 | public TerminalPolicyVersionCreated( 19 | int versionNumber, 20 | int baseVersionNumber, 21 | DateTime versionFrom, 22 | DateTime versionTo, 23 | DateTime coverFrom, 24 | DateTime coverTo, 25 | List covers) 26 | { 27 | VersionNumber = versionNumber; 28 | BaseVersionNumber = baseVersionNumber; 29 | VersionFrom = versionFrom; 30 | VersionTo = versionTo; 31 | CoverFrom = coverFrom; 32 | CoverTo = coverTo; 33 | Covers = covers; 34 | } 35 | 36 | public TerminalPolicyVersionCreated( 37 | int versionNumber, 38 | int baseVersionNumber, 39 | DateTime versionFrom, 40 | DateTime versionTo, 41 | DateTime coverFrom, 42 | DateTime coverTo, 43 | List covers) 44 | { 45 | VersionNumber = versionNumber; 46 | BaseVersionNumber = baseVersionNumber; 47 | VersionFrom = versionFrom; 48 | VersionTo = versionTo; 49 | CoverFrom = coverFrom; 50 | CoverTo = coverTo; 51 | Covers = covers 52 | .Select(c => new PolicyCoverData(c.CoverCode, c.CoverPeriod.ValidFrom, c.CoverPeriod.ValidTo, c.Amount, 53 | c.Price.Price, c.Price.PricePeriod)) 54 | .ToList(); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /NoCqrs.Tests/PolicyAnnexesUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NoCqrs.Domain; 4 | using NodaMoney; 5 | using Xunit; 6 | using static Xunit.Assert; 7 | 8 | namespace NoCqrs.Tests 9 | { 10 | public class PolicyAnnexesUnitTest 11 | { 12 | [Fact] 13 | public void CanExtendCoverageWithFirstDayOfPolicy() 14 | { 15 | 16 | } 17 | 18 | [Fact] 19 | public void CanExtendCoverageWithLastDayOfPolicy() 20 | { 21 | } 22 | 23 | [Fact] 24 | public void CanExtendCoverageWithMiddleDayOfPolicy() 25 | { 26 | var product = ProductsTestData.StandardCarInsurance(); 27 | var policy = PolicyTestData.StandardOneYearPolicy(new DateTime(2019, 1, 1)); 28 | 29 | var newCover = product.Covers.WithCode("AC"); 30 | policy.ExtendCoverage 31 | ( 32 | new DateTime(2019, 7, 1), 33 | new CoverPrice(Guid.NewGuid(),newCover, Money.Euro(100) , TimeSpan.FromDays(365)) 34 | ); 35 | 36 | Equal(2, policy.Versions.Count()); 37 | Equal(Money.Euro(500), policy.Versions.WithNumber(1).TotalPremium); 38 | Equal(PolicyVersionStatus.Active, policy.Versions.WithNumber(1).VersionStatus); 39 | Equal(Money.Euro(550.41), policy.Versions.WithNumber(2).TotalPremium); 40 | Equal(PolicyVersionStatus.Draft, policy.Versions.WithNumber(2).VersionStatus); 41 | Equal(PolicyVersionStatus.Draft, policy.Versions.WithNumber(2).VersionStatus); 42 | } 43 | 44 | [Fact] 45 | public void CanExtendCoverageOverridingPreviousExtension() 46 | { 47 | } 48 | 49 | [Fact] 50 | public void CanExtendCoverageAddingToPreviousExtension() 51 | { 52 | } 53 | 54 | [Fact] 55 | public void CannotExtendCoverageOnTerminatedPolicy() 56 | { 57 | } 58 | 59 | [Fact] 60 | public void CannotExtendCoverageWithEffectiveDateBeforePolicyStart() 61 | { 62 | } 63 | 64 | [Fact] 65 | public void CannotExtendCoverageWithEffectiveDateAfterPolicyEnd() 66 | { 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /SeparateModels/Queries/FindPoliciesHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using Dapper; 6 | using MediatR; 7 | using Npgsql; 8 | using SeparateModels.Domain; 9 | using SeparateModels.ReadModels; 10 | using SeparateModels.Services; 11 | 12 | namespace SeparateModels.Queries 13 | { 14 | public class FindPoliciesHandler : IRequestHandler> 15 | { 16 | private readonly PolicyInfoDtoFinder policyInfoDtoFinder; 17 | 18 | public FindPoliciesHandler(PolicyInfoDtoFinder policyInfoDtoFinder) 19 | { 20 | this.policyInfoDtoFinder = policyInfoDtoFinder; 21 | } 22 | 23 | public Task> Handle(FindPoliciesQuery query, CancellationToken cancellationToken) 24 | { 25 | var policyFilter = new PolicyFilter 26 | ( 27 | query.PolicyNumber, 28 | query.PolicyHolder, 29 | query.PolicyStartFrom, 30 | query.PolicyStartTo, 31 | query.CarPlateNumber 32 | ); 33 | 34 | return Task.FromResult(policyInfoDtoFinder.FindByFilter(policyFilter)); 35 | /* 36 | using (var cn = 37 | new NpgsqlConnection( 38 | "User ID=lab_user;Password=lab_pass;Database=lab_cqrs_dotnet_demo;Host=localhost;Port=5432")) 39 | { 40 | return Task.FromResult(cn.Query("select " + 41 | "policy_id as PolicyId," + 42 | "policy_number as PolicyNumber," + 43 | "cover_from as CoverFrom," + 44 | "cover_to as CoverTo," + 45 | "vehicle as Vehicle," + 46 | "policy_holder as PolicyHolder," + 47 | "total_premium as TotalPremiumAmount " + 48 | "from public.policy_info_view").ToList()); 49 | }*/ 50 | 51 | 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /CqrsWithEs.Tests/PolicyTerminationUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CqrsWithEs.Domain; 5 | using CqrsWithEs.Domain.Policy; 6 | using CqrsWithEs.Domain.Policy.Events; 7 | using CqrsWithEs.Tests.Asserts; 8 | using KellermanSoftware.CompareNetObjects; 9 | using NodaMoney; 10 | using Xunit; 11 | using Xunit.Asserts.Compare; 12 | using static Xunit.Assert; 13 | 14 | namespace CqrsWithEs.Tests 15 | { 16 | public class PolicyTerminationUnitTests 17 | { 18 | [Fact] 19 | public void CanTerminatePolicyInTheMiddle() 20 | { 21 | var policy = PolicyTestData.StandardOneYearPolicy(new DateTime(2019, 1, 1)); 22 | var terminationDate = new DateTime(2019, 7, 1); 23 | 24 | policy.TerminatePolicy(terminationDate); 25 | var resultingEvents = policy.GetUncommittedChanges(); 26 | 27 | //assert state 28 | policy 29 | .Should() 30 | .HaveVersions(2); 31 | 32 | policy.Versions.WithNumber(1).Should() 33 | .BeActive() 34 | .HaveActivePolicyStatus() 35 | .HaveTotalPremiumEqualTo(Money.Euro(500)); 36 | 37 | policy.Versions.WithNumber(2) 38 | .Should() 39 | .BeDraft() 40 | .HaveTerminatedPolicyStatus() 41 | .CoverPeriod(new DateTime(2019,1,1), terminationDate.AddDays(-1)) 42 | .HaveTotalPremiumEqualTo(Money.Euro(246.58)); 43 | 44 | 45 | //assert events 46 | resultingEvents 47 | .Should() 48 | .BeSingle() 49 | .ContainEvent( 50 | new TerminalPolicyVersionCreated 51 | ( 52 | 2, 53 | 1, 54 | new DateTime(2019,7,1), 55 | new DateTime(2020,1,1), 56 | new DateTime(2019,1,1), 57 | new DateTime(2019,6,30), 58 | new List 59 | { 60 | new PolicyCoverData("OC",new DateTime(2019,1,1),new DateTime(2019,6,30),Money.Euro(246.58),Money.Euro(500),TimeSpan.FromDays(365)) 61 | } 62 | ) 63 | ); 64 | } 65 | 66 | } 67 | } -------------------------------------------------------------------------------- /CqrsWithEs.Tests/BuyAdditionalCoverHandlerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Reflection.Metadata; 4 | using System.Threading; 5 | using CqrsWithEs.Commands; 6 | using CqrsWithEs.DataAccess; 7 | using CqrsWithEs.Domain.Base; 8 | using CqrsWithEs.Domain.Policy.Events; 9 | using MediatR; 10 | using Moq; 11 | using NodaMoney; 12 | using Xunit; 13 | 14 | namespace CqrsWithEs.Tests 15 | { 16 | public class BuyAdditionalCoverHandlerTest 17 | { 18 | [Fact] 19 | public async void CanBuyAdditionalCover() 20 | { 21 | var policyId = new Guid("6522675f-710b-4614-985a-1a1d938a0a2c"); 22 | var handler = new BuyAdditionalCoverHandler 23 | ( 24 | new InMemoryPolicyRepository 25 | ( 26 | StoreWithEvents(policyId, new List 27 | { 28 | new InitialPolicyVersionCreated 29 | ( 30 | "POL001", 31 | "P1", 32 | new DateTime(2019,1,1), 33 | new DateTime(2020,1,1), 34 | new DateTime(2019,1,1), 35 | new PersonData("A","A","A"), 36 | new CarData("A","A",2010), 37 | new List 38 | { 39 | new PolicyCoverData("OC", new DateTime(2019,1,1),new DateTime(2020,1,1), Money.Euro(500), Money.Euro(500),TimeSpan.FromDays(365)) 40 | } 41 | ) 42 | }) 43 | , new Mock().Object 44 | ) 45 | ); 46 | 47 | var cmdResult = await handler.Handle( 48 | new BuyAdditionalCoverCommand 49 | { 50 | PolicyId = policyId, 51 | NewCoverCode = "AC", 52 | NewCoverPrice = 200M, 53 | NewCoverPriceUnit = TimeSpan.FromDays(365), 54 | EffectiveDateOfChange = new DateTime(2019,6,1) 55 | }, 56 | CancellationToken.None); 57 | 58 | Assert.Equal(2, cmdResult.VersionWithAdditionalCoversNumber); 59 | } 60 | 61 | 62 | private IEventStore StoreWithEvents(Guid id, IEnumerable events) 63 | { 64 | var store = new EventStore(); 65 | store.Bus = new Mock().Object; 66 | store.SaveEvents(id, events, -1); 67 | return store; 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /NoCqrs/Controllers/PoliciesController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using NoCqrs.Services; 3 | 4 | namespace NoCqrs.Controllers 5 | { 6 | [Route("api/[controller]")] 7 | [ApiController] 8 | public class PoliciesController : ControllerBase 9 | { 10 | private readonly PolicyService policyService; 11 | 12 | public PoliciesController(PolicyService policyService) 13 | { 14 | this.policyService = policyService; 15 | } 16 | 17 | [HttpPost] 18 | public IActionResult CreatePolicy([FromBody] CreatePolicyRequest request) 19 | { 20 | var result = policyService.CreatePolicy(request); 21 | return Ok(result); 22 | } 23 | 24 | [HttpPost("/BuyAdditionalCover")] 25 | public IActionResult BuyAdditionalCover([FromBody] BuyAdditionalCoverRequest request) 26 | { 27 | var result = policyService.BuyAdditionalCover(request); 28 | return Ok(result); 29 | } 30 | 31 | [HttpPost("/ConfirmBuyAdditionalCover")] 32 | public IActionResult Post([FromBody] ConfirmBuyAdditionalCoverRequest request) 33 | { 34 | var result = policyService.ConfirmBuyAdditionalCover(request); 35 | return Ok(result); 36 | } 37 | 38 | [HttpPost("/Terminate")] 39 | public IActionResult Terminate([FromBody] TerminatePolicyRequest request) 40 | { 41 | var result = policyService.TerminatePolicy(request); 42 | return Ok(result); 43 | } 44 | 45 | [HttpPost("/ConfirmTermination")] 46 | public IActionResult ConfirmTermination([FromBody] ConfirmTerminationRequest request) 47 | { 48 | var result = policyService.ConfirmTermination(request); 49 | return Ok(result); 50 | } 51 | 52 | [HttpPost("/CancelLastAnnex")] 53 | public IActionResult CancelLastAnnex([FromBody] CancelLastAnnexRequest request) 54 | { 55 | var result = policyService.CancelLastAnnex(request); 56 | return Ok(result); 57 | } 58 | 59 | [HttpGet("/{policyNumber}")] 60 | public IActionResult GetPolicyDetails(string policyNumber) 61 | { 62 | var result = policyService.GetPolicyDetails(policyNumber); 63 | return Ok(result); 64 | } 65 | 66 | 67 | [HttpPost("/find")] 68 | public IActionResult Find([FromBody] SearchPolicyRequest searchPolicyRequest) 69 | { 70 | var result = policyService.SearchPolicies(searchPolicyRequest); 71 | return Ok(result); 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /NoCqrs/DataAccess/InsuranceDbContext.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using NoCqrs.Domain; 3 | 4 | namespace NoCqrs.DataAccess 5 | { 6 | public class InsuranceDbContext : DbContext 7 | { 8 | public DbSet Products { get; set; } 9 | public DbSet Offers { get; set; } 10 | public DbSet Policies { get; set; } 11 | 12 | public InsuranceDbContext(DbContextOptions options) : base(options) 13 | { 14 | 15 | } 16 | 17 | protected override void OnModelCreating(ModelBuilder modelBuilder) 18 | { 19 | modelBuilder.Entity(p => 20 | { 21 | p.HasKey(product => product.Id); 22 | var partsNav = p.Metadata.FindNavigation(nameof(Product.Covers)); 23 | partsNav.SetPropertyAccessMode(PropertyAccessMode.Field); 24 | }); 25 | 26 | modelBuilder.Entity(o => 27 | { 28 | o.HasKey(offer => offer.Id); 29 | var partsNav = o.Metadata.FindNavigation(nameof(Offer.Covers)); 30 | partsNav.SetPropertyAccessMode(PropertyAccessMode.Field); 31 | 32 | o.OwnsOne(offer => offer.Customer); 33 | o.OwnsOne(offer => offer.Driver); 34 | o.OwnsOne(offer => offer.Car); 35 | 36 | }); 37 | 38 | modelBuilder.Entity(p => 39 | { 40 | p.HasKey(policy => policy.Id); 41 | var partsNav = p.Metadata.FindNavigation(nameof(Policy.Versions)); 42 | partsNav.SetPropertyAccessMode(PropertyAccessMode.Field); 43 | }); 44 | 45 | modelBuilder.Entity(pv => 46 | { 47 | pv.HasKey(policyVersion => policyVersion.Id); 48 | var partsNav = pv.Metadata.FindNavigation(nameof(PolicyVersion.Covers)); 49 | partsNav.SetPropertyAccessMode(PropertyAccessMode.Field); 50 | 51 | pv.OwnsOne(policyVersion => policyVersion.PolicyHolder); 52 | pv.OwnsOne(policyVersion => policyVersion.Driver); 53 | pv.OwnsOne(policyVersion => policyVersion.Car); 54 | 55 | pv.OwnsOne(policyVersion => policyVersion.CoverPeriod); 56 | pv.OwnsOne(policyVersion => policyVersion.VersionValidityPeriod); 57 | }); 58 | 59 | modelBuilder.Entity(pc => 60 | { 61 | pc.HasKey(c => c.Id); 62 | pc.OwnsOne(c => c.CoverPeriod); 63 | }); 64 | 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /SeparateModels/ReadModels/PolicyInfoDtoFinder.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Text; 4 | using Dapper; 5 | using Npgsql; 6 | using static System.String; 7 | 8 | namespace SeparateModels.ReadModels 9 | { 10 | public class PolicyInfoDtoFinder 11 | { 12 | private readonly string cnString; 13 | 14 | private readonly string baseQuery = "select " + 15 | "policy_id as PolicyId," + 16 | "policy_number as PolicyNumber," + 17 | "cover_from as CoverFrom," + 18 | "cover_to as CoverTo," + 19 | "vehicle as Vehicle," + 20 | "policy_holder as PolicyHolder," + 21 | "total_premium as TotalPremiumAmount " + 22 | "from public.policy_info_view " + 23 | "where 1 = 1 "; 24 | 25 | public PolicyInfoDtoFinder(string cnString) 26 | { 27 | this.cnString = cnString; 28 | } 29 | 30 | public IList FindByFilter(PolicyFilter filter) 31 | { 32 | using (var cn = new NpgsqlConnection(cnString)) 33 | { 34 | var sql = BuildSql(filter); 35 | return cn 36 | .Query(sql, filter) 37 | .ToList(); 38 | } 39 | } 40 | 41 | private string BuildSql(PolicyFilter filter) 42 | { 43 | var sql = new StringBuilder(); 44 | 45 | sql.Append(baseQuery); 46 | 47 | if (!IsNullOrWhiteSpace(filter.PolicyNumber)) 48 | { 49 | sql.Append("and policy_number = @PolicyNumber"); 50 | } 51 | 52 | if (filter.PolicyStartDateFrom.HasValue) 53 | { 54 | sql.Append("and cover_from >= @PolicyStartDateFrom"); 55 | } 56 | 57 | if (filter.PolicyStartDateTo.HasValue) 58 | { 59 | sql.Append("and cover_to <= @PolicyStartDateTo"); 60 | } 61 | 62 | if (!IsNullOrWhiteSpace(filter.CarPlateNumber)) 63 | { 64 | sql.Append("and vehicle LIKE @CarPlateNumber || ' %'"); 65 | } 66 | 67 | if (!IsNullOrWhiteSpace(filter.PolicyHolder)) 68 | { 69 | sql.Append("and policy_holder = @PolicyHolder"); 70 | } 71 | 72 | return sql.ToString(); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /NoCqrs/DataAccess/EFPolicyRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Linq; 4 | using Microsoft.EntityFrameworkCore; 5 | using NoCqrs.Domain; 6 | using NoCqrs.Utils; 7 | 8 | namespace NoCqrs.DataAccess 9 | { 10 | public class EFPolicyRepository : IPolicyRepository 11 | { 12 | private readonly InsuranceDbContext dbContext; 13 | 14 | public EFPolicyRepository(InsuranceDbContext dbContext) 15 | { 16 | this.dbContext = dbContext; 17 | } 18 | 19 | public Policy WithNumber(string number) 20 | { 21 | return dbContext 22 | .Policies 23 | .Include(p => p.Product) 24 | .Include(p => p.Versions) 25 | .ThenInclude(pv => pv.Covers) 26 | .ThenInclude(c => c.Cover) 27 | .FirstOrDefault(p => p.Number == number); 28 | } 29 | 30 | public void Add(Policy policy) 31 | { 32 | dbContext.Policies.Add(policy); 33 | } 34 | 35 | public IList Find(PolicyFilter filter) 36 | { 37 | var query = dbContext.Policies.AsQueryable(); 38 | 39 | if (filter.PolicyNumber.NotBlank()) 40 | { 41 | query = query.Where(p => p.Number == filter.PolicyNumber); 42 | } 43 | 44 | if (filter.CarPlateNumber.NotBlank()) 45 | { 46 | query = query.Where(p => p.CurrentVersion.Car.PlateNumber == filter.CarPlateNumber); 47 | } 48 | 49 | if (filter.PolicyHolderFirstName.NotBlank()) 50 | { 51 | query = query.Where(p => p.CurrentVersion.PolicyHolder.FirstName == filter.PolicyHolderFirstName); 52 | } 53 | 54 | if (filter.PolicyHolderLastName.NotBlank()) 55 | { 56 | query = query.Where(p => p.CurrentVersion.PolicyHolder.LastName== filter.PolicyHolderLastName); 57 | } 58 | 59 | if (filter.PolicyStartDateFrom.HasValue) 60 | { 61 | query = query.Where(p => p.CurrentVersion.CoverPeriod.ValidFrom >= filter.PolicyStartDateFrom.Value); 62 | } 63 | 64 | if (filter.PolicyStartDateTo.HasValue) 65 | { 66 | query = query.Where(p => p.CurrentVersion.CoverPeriod.ValidFrom <= filter.PolicyStartDateTo.Value); 67 | } 68 | 69 | query = query 70 | .Include(p => p.Product) 71 | .Include(p => p.Versions) 72 | .ThenInclude(pv => pv.Covers) 73 | .ThenInclude(c => c.Cover); 74 | 75 | return query.ToList(); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /CqrsWithEs.Tests/HttpClientAsserts/HttpClientExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net.Http; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using Newtonsoft.Json; 6 | 7 | namespace CqrsWithEs.Tests.HttpClientAsserts 8 | { 9 | static class HttpClientExtensions 10 | { 11 | public static async Task> DoPostAsync(this HttpClient client, string uri, Object data) 12 | where T : class 13 | { 14 | var requestContent = new StringContent( 15 | JsonConvert.SerializeObject(data), 16 | Encoding.UTF8, 17 | "application/json" 18 | ); 19 | 20 | var response = await client.PostAsync(uri, requestContent).ConfigureAwait(false); 21 | 22 | if (response.StatusCode == System.Net.HttpStatusCode.OK) 23 | { 24 | var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); 25 | var result = JsonConvert.DeserializeObject(responseContent); 26 | return RestResult.Ok(result); 27 | } 28 | else 29 | { 30 | var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); 31 | var errorDetails = JsonConvert.DeserializeAnonymousType(responseContent, new { Code = "", Message = "" }); 32 | return RestResult.Error(errorDetails.Code, errorDetails.Message); 33 | } 34 | 35 | } 36 | 37 | 38 | public static async Task DoGetAsync(this HttpClient client, string uri) 39 | where T : class 40 | { 41 | var response = await client.GetAsync(uri).ConfigureAwait(false); 42 | response.EnsureSuccessStatusCode(); 43 | var responseContent = await response.Content.ReadAsStringAsync().ConfigureAwait(false); 44 | var result = JsonConvert.DeserializeObject(responseContent); 45 | 46 | return result; 47 | } 48 | } 49 | 50 | public class RestResult 51 | { 52 | public T Data { get; set; } 53 | public bool Success { get; set; } 54 | public string ErrorMessage { get; set; } 55 | public string ErrorCode { get; set; } 56 | 57 | public static RestResult Ok(T data) 58 | { 59 | return new RestResult 60 | { 61 | Data = data, 62 | Success = true 63 | }; 64 | } 65 | 66 | public static RestResult Error(string code, string message) 67 | { 68 | return new RestResult 69 | { 70 | Success = false, 71 | ErrorCode = code, 72 | ErrorMessage = message 73 | }; 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /DotNetCqrsIntro.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoCqrs", "NoCqrs\NoCqrs.csproj", "{824424B9-910F-46BC-B281-64E51BC50DD2}" 4 | EndProject 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NoCqrs.Tests", "NoCqrs.Tests\NoCqrs.Tests.csproj", "{7AD6C0F6-8263-4327-AA0A-1D30D9AB4A77}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SeparateModels", "SeparateModels\SeparateModels.csproj", "{723116C0-CEB4-4B22-A127-DDAD5D339824}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CqrsWithEs", "CqrsWithEs\CqrsWithEs.csproj", "{B8FCAE52-5FA4-47E2-B91D-6970A9E94268}" 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CqrsWithEs.Tests", "CqrsWithEs.Tests\CqrsWithEs.Tests.csproj", "{2DE1BB20-E2A8-4427-9463-87AFDE399AAB}" 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|Any CPU = Debug|Any CPU 16 | Release|Any CPU = Release|Any CPU 17 | EndGlobalSection 18 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 19 | {824424B9-910F-46BC-B281-64E51BC50DD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {824424B9-910F-46BC-B281-64E51BC50DD2}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {824424B9-910F-46BC-B281-64E51BC50DD2}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {824424B9-910F-46BC-B281-64E51BC50DD2}.Release|Any CPU.Build.0 = Release|Any CPU 23 | {7AD6C0F6-8263-4327-AA0A-1D30D9AB4A77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {7AD6C0F6-8263-4327-AA0A-1D30D9AB4A77}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {7AD6C0F6-8263-4327-AA0A-1D30D9AB4A77}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {7AD6C0F6-8263-4327-AA0A-1D30D9AB4A77}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {723116C0-CEB4-4B22-A127-DDAD5D339824}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {723116C0-CEB4-4B22-A127-DDAD5D339824}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {723116C0-CEB4-4B22-A127-DDAD5D339824}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {723116C0-CEB4-4B22-A127-DDAD5D339824}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {B8FCAE52-5FA4-47E2-B91D-6970A9E94268}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {B8FCAE52-5FA4-47E2-B91D-6970A9E94268}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {B8FCAE52-5FA4-47E2-B91D-6970A9E94268}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {B8FCAE52-5FA4-47E2-B91D-6970A9E94268}.Release|Any CPU.Build.0 = Release|Any CPU 35 | {2DE1BB20-E2A8-4427-9463-87AFDE399AAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {2DE1BB20-E2A8-4427-9463-87AFDE399AAB}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {2DE1BB20-E2A8-4427-9463-87AFDE399AAB}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {2DE1BB20-E2A8-4427-9463-87AFDE399AAB}.Release|Any CPU.Build.0 = Release|Any CPU 39 | EndGlobalSection 40 | EndGlobal 41 | -------------------------------------------------------------------------------- /NoCqrs/Services/PolicyDtoAssembler.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using NoCqrs.Domain; 4 | 5 | namespace NoCqrs.Services 6 | { 7 | public static class PolicyDtoAssembler 8 | { 9 | public static PolicyDto AssemblePolicyDto(Policy policy) 10 | { 11 | return new PolicyDto 12 | { 13 | PolicyId = policy.Id, 14 | PolicyNumber = policy.Number, 15 | CurrentVersion = PolicyVersionDtoAssembler.AssemblePolicyVersionDto(policy, policy.CurrentVersion), 16 | Versions = policy.Versions.Select(v => PolicyVersionDtoAssembler.AssemblePolicyVersionDto(policy, v)).ToList() 17 | }; 18 | } 19 | } 20 | 21 | public static class PolicyVersionDtoAssembler 22 | { 23 | public static PolicyVersionDto AssemblePolicyVersionDto(Policy policy, PolicyVersion version) 24 | { 25 | return new PolicyVersionDto 26 | { 27 | VersionNumber = version.VersionNumber, 28 | VersionStatus = version.VersionStatus.ToString(), 29 | PolicyStatus = version.PolicyStatus.ToString(), 30 | PolicyHolder = $"{version.PolicyHolder.LastName} {version.PolicyHolder.FirstName}", 31 | Insured = null, 32 | Car = $"{version.Car.PlateNumber} {version.Car.Make} {version.Car.ProductionYear}", 33 | CoverFrom = version.CoverPeriod.ValidFrom, 34 | CoverTo = version.CoverPeriod.ValidTo, 35 | VersionFrom = version.VersionValidityPeriod.ValidFrom, 36 | VersionTo = version.VersionValidityPeriod.ValidTo, 37 | TotalPremium = version.TotalPremium.Amount, 38 | Covers = version.Covers.Select(CoverDtoAssembler.AssembleCoverDto).ToList(), 39 | Changes = AssembleChanges(policy, version) 40 | }; 41 | } 42 | 43 | private static List AssembleChanges(Policy policy, PolicyVersion version) 44 | { 45 | if (!version.BaseVersionNumber.HasValue) 46 | return new List() {}; 47 | 48 | var baseVersion = policy.Versions.WithNumber(version.BaseVersionNumber.Value); 49 | 50 | return PolicyVersionComparer.Of(version, baseVersion).Compare(); 51 | } 52 | } 53 | 54 | public static class CoverDtoAssembler 55 | { 56 | public static CoverDto AssembleCoverDto(PolicyCover policyCover) 57 | { 58 | return new CoverDto 59 | { 60 | Code = policyCover.Cover.Code, 61 | CoverFrom = policyCover.CoverPeriod.ValidFrom, 62 | CoverTo = policyCover.CoverPeriod.ValidTo, 63 | PremiumAmount = policyCover.Amount.Amount 64 | }; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Policy/Events/InitialPolicyVersionCreated.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CqrsWithEs.Domain.Base; 5 | using CqrsWithEs.Domain.Common; 6 | 7 | namespace CqrsWithEs.Domain.Policy.Events 8 | { 9 | public class InitialPolicyVersionCreated : Event 10 | { 11 | public string PolicyNumber { get; private set; } 12 | public PolicyStatus PolicyStatus { get; private set; } 13 | public string ProductCode { get; private set; } 14 | public DateTime CoverFrom { get; private set; } 15 | public DateTime CoverTo{ get; private set; } 16 | public DateTime PurchaseDate { get; private set; } 17 | public PersonData PolicyHolder { get; private set; } 18 | public CarData Car { get; private set; } 19 | public List Covers { get; private set; } 20 | 21 | public InitialPolicyVersionCreated( 22 | string policyNumber, 23 | string productCode, 24 | DateTime coverFrom, 25 | DateTime coverTo, 26 | DateTime purchaseDate, 27 | PersonData policyHolder, 28 | CarData car, 29 | List covers) 30 | { 31 | PolicyNumber = policyNumber; 32 | PolicyStatus = PolicyStatus.Active; 33 | ProductCode = productCode; 34 | CoverFrom = coverFrom; 35 | CoverTo = coverTo; 36 | PurchaseDate = purchaseDate; 37 | PolicyHolder = policyHolder; 38 | Car = car; 39 | Covers = covers; 40 | } 41 | 42 | public InitialPolicyVersionCreated( 43 | string policyNumber, 44 | string productCode, 45 | ValidityPeriod coverPeriod, 46 | DateTime purchaseDate, 47 | Person policyHolder, 48 | Car car, 49 | IEnumerable covers) 50 | { 51 | PolicyNumber = policyNumber; 52 | PolicyStatus = PolicyStatus.Active; 53 | ProductCode = productCode; 54 | CoverFrom = coverPeriod.ValidFrom; 55 | CoverTo = coverPeriod.ValidTo; 56 | PurchaseDate = purchaseDate; 57 | PolicyHolder = new PersonData(policyHolder.FirstName, policyHolder.LastName, policyHolder.TaxId); 58 | Car = new CarData(car.Make,car.PlateNumber,car.ProductionYear); 59 | Covers = covers 60 | .Select(c => new PolicyCoverData 61 | ( 62 | c.CoverCode, 63 | c.CoverPeriod.ValidFrom, 64 | c.CoverPeriod.ValidTo, 65 | c.Amount, 66 | c.Price.Price, 67 | c.Price.PricePeriod 68 | ) 69 | ) 70 | .ToList(); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /NoCqrs.Tests/PolicyCreationUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using NoCqrs.Domain; 4 | using NodaMoney; 5 | using Xunit; 6 | using static Xunit.Assert; 7 | 8 | namespace NoCqrs.Tests 9 | { 10 | public class PolicyCreationUnitTest 11 | { 12 | [Fact] 13 | public void CanConvertOfferToPolicyBeforeItExpires() 14 | { 15 | var offer = OffersTestData.StandardOneYearOCOfferValidUntil(new DateTime(2019, 1, 31)); 16 | 17 | var policy = Policy.ConvertOffer(offer,"POL0001", new DateTime(2019,1,12), new DateTime(2019,1,15)); 18 | 19 | NotNull(policy); 20 | Single(policy.Versions); 21 | Equal(PolicyStatus.Active, policy.Versions.WithNumber(1).PolicyStatus); 22 | Equal(PolicyVersionStatus.Active, policy.Versions.WithNumber(1).VersionStatus); 23 | Equal(Money.Euro(500), policy.Versions.WithNumber(1).TotalPremium); 24 | } 25 | 26 | [Fact] 27 | public void CannotConvertOfferToPolicyAfterItExpires() 28 | { 29 | var offer = OffersTestData.StandardOneYearOCOfferValidUntil(new DateTime(2019, 1, 10)); 30 | 31 | var ex = Throws(() => Policy.ConvertOffer(offer, "POL0001", new DateTime(2019, 1, 12), 32 | new DateTime(2019, 1, 15))); 33 | 34 | Equal("Offer expired", ex.Message); 35 | } 36 | 37 | [Fact] 38 | public void CannotConvertOfferToPolicyAfterItsConverted() 39 | { 40 | var offer = OffersTestData.ConvertedOfferValidUntil(new DateTime(2019, 1, 10)); 41 | 42 | var ex = Throws(() => Policy.ConvertOffer(offer, "POL0001", new DateTime(2019, 1, 10), 43 | new DateTime(2019, 1, 10))); 44 | 45 | Equal("Offer already converted", ex.Message); 46 | } 47 | 48 | [Fact] 49 | public void CannotConvertOfferToPolicyAfterItsRejected() 50 | { 51 | var offer = OffersTestData.RejectedOfferValidUntil(new DateTime(2019, 1, 10)); 52 | 53 | var ex = Throws(() => Policy.ConvertOffer(offer, "POL0001", new DateTime(2019, 1, 10), 54 | new DateTime(2019, 1, 10))); 55 | 56 | Equal("Offer already rejected", ex.Message); 57 | } 58 | 59 | [Fact] 60 | public void CannotConvertStartPolicyAfterOfferExpiryDate() 61 | { 62 | var offer = OffersTestData.StandardOneYearOCOfferValidUntil(new DateTime(2019, 1, 10)); 63 | 64 | var ex = Throws(() => Policy.ConvertOffer(offer, "POL0001", new DateTime(2019, 1, 10), 65 | new DateTime(2019, 1, 15))); 66 | 67 | Equal("Offer not valid at policy start date", ex.Message); 68 | } 69 | 70 | } 71 | } -------------------------------------------------------------------------------- /CqrsWithEs/Domain/Policy/PolicyVersion.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CqrsWithEs.Domain.Common; 5 | using NodaMoney; 6 | 7 | namespace CqrsWithEs.Domain.Policy 8 | { 9 | public class PolicyVersion : IPolicyState 10 | { 11 | public int VersionNumber { get; } 12 | public PolicyStatus PolicyStatus { get; } 13 | public PolicyVersionStatus VersionStatus { get; private set; } 14 | public ValidityPeriod CoverPeriod { get; } 15 | public ValidityPeriod VersionPeriod { get; } 16 | private List covers = new List(); 17 | public IReadOnlyCollection Covers => covers.AsReadOnly(); 18 | public Money TotalPremium => covers.Aggregate(Money.Euro(0), (sum, c) => sum + c.Amount); 19 | 20 | public PolicyVersion( 21 | int versionNumber, 22 | PolicyStatus policyStatus, 23 | PolicyVersionStatus versionStatus, 24 | ValidityPeriod coverPeriod, 25 | ValidityPeriod versionPeriod, 26 | IEnumerable policyCovers) 27 | { 28 | VersionNumber = versionNumber; 29 | PolicyStatus = policyStatus; 30 | VersionStatus = versionStatus; 31 | CoverPeriod = coverPeriod; 32 | VersionPeriod = versionPeriod; 33 | covers.AddRange(policyCovers); 34 | } 35 | 36 | public bool IsEffectiveOn(DateTime theDate) => VersionPeriod.Contains(theDate); 37 | 38 | public bool CoversDate(DateTime theDate) => CoverPeriod.Contains(theDate); 39 | 40 | public PolicyVersion CreateDraftCopy(int newVersionNumber, ValidityPeriod versionPeriod) 41 | { 42 | return new PolicyVersion 43 | ( 44 | newVersionNumber, 45 | PolicyStatus.Active, 46 | PolicyVersionStatus.Draft, 47 | CoverPeriod, 48 | versionPeriod, 49 | new List(covers) 50 | ); 51 | } 52 | 53 | public void AddCover(string coverCode, UnitPrice price, ValidityPeriod coverPeriod) 54 | { 55 | if (VersionStatus != PolicyVersionStatus.Draft) 56 | { 57 | throw new ApplicationException("Cannot modify non draft version"); 58 | } 59 | 60 | //check if not already present?? 61 | 62 | //TODO: check dates 63 | 64 | this.covers.Add(new PolicyCover(coverCode,coverPeriod,price)); 65 | } 66 | 67 | public bool ContainsCover(string coverCode) => covers.Any(c => c.CoverCode == coverCode); 68 | 69 | public void Confirm() 70 | { 71 | if (VersionStatus != PolicyVersionStatus.Draft) 72 | { 73 | throw new ApplicationException("Only draft can be confirmed"); 74 | } 75 | 76 | this.VersionStatus = PolicyVersionStatus.Active; 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /SeparateModels/ReadModels/PolicyInfoDtoProjection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Dapper; 3 | using Npgsql; 4 | using SeparateModels.Domain; 5 | 6 | namespace SeparateModels.ReadModels 7 | { 8 | public class PolicyInfoDtoProjection 9 | { 10 | private readonly string cnString; 11 | 12 | public PolicyInfoDtoProjection(string cnString) 13 | { 14 | this.cnString = cnString; 15 | } 16 | 17 | public void CreatePolicyInfoDto(Policy policy) 18 | { 19 | using (var cn = new NpgsqlConnection(cnString)) 20 | { 21 | var version = policy.Versions.WithNumber(1); 22 | 23 | var policyInfo = new PolicyInfoDto 24 | { 25 | PolicyId = policy.Id, 26 | PolicyNumber = policy.Number, 27 | CoverFrom = version.CoverPeriod.ValidFrom, 28 | CoverTo = version.CoverPeriod.ValidTo, 29 | PolicyHolder = $"{version.PolicyHolder.LastName} {version.PolicyHolder.FirstName}", 30 | Vehicle = $"{version.Car.PlateNumber} {version.Car.Make}", 31 | TotalPremiumAmount = version.TotalPremium.Amount 32 | }; 33 | 34 | cn.Open(); 35 | cn.Execute( 36 | "INSERT INTO public.policy_info_view (policy_id,policy_number,cover_from,cover_to,vehicle,policy_holder,total_premium) " + 37 | "VALUES (@PolicyId,@PolicyNumber,@CoverFrom,@CoverTo,@Vehicle,@PolicyHolder,@TotalPremiumAmount)", 38 | policyInfo); 39 | } 40 | } 41 | 42 | public void UpdatePolicyInfoDto(Policy policy, PolicyVersion currentVersion) 43 | { 44 | using (var cn = new NpgsqlConnection(cnString)) 45 | { 46 | var policyInfo = new PolicyInfoDto 47 | { 48 | PolicyId = policy.Id, 49 | CoverFrom = currentVersion.CoverPeriod.ValidFrom, 50 | CoverTo = currentVersion.CoverPeriod.ValidTo, 51 | PolicyHolder = $"{currentVersion.PolicyHolder.LastName} {currentVersion.PolicyHolder.FirstName}", 52 | Vehicle = $"{currentVersion.Car.PlateNumber} {currentVersion.Car.Make}", 53 | TotalPremiumAmount = currentVersion.TotalPremium.Amount 54 | }; 55 | 56 | 57 | cn.Open(); 58 | cn.Execute( 59 | "UPDATE public.policy_info_view " + 60 | "SET " + 61 | "cover_from = @CoverFrom, " + 62 | "cover_to = @CoverTo, " + 63 | "vehicle = @Vehicle, " + 64 | "policy_holder = @PolicyHolder, " + 65 | "total_premium = @TotalPremiumAmount " + 66 | "WHERE policy_id = @PolicyId ", 67 | policyInfo); 68 | 69 | } 70 | } 71 | 72 | } 73 | } -------------------------------------------------------------------------------- /CqrsWithEs.Tests/PolicyAnnexUnitTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using CqrsWithEs.Domain; 4 | using CqrsWithEs.Domain.Common; 5 | using CqrsWithEs.Domain.Offer; 6 | using CqrsWithEs.Domain.Policy; 7 | using CqrsWithEs.Domain.Policy.Events; 8 | using CqrsWithEs.Tests.Asserts; 9 | using NodaMoney; 10 | using Xunit; 11 | using static Xunit.Assert; 12 | 13 | namespace CqrsWithEs.Tests 14 | { 15 | public class PolicyAnnexUnitTests 16 | { 17 | [Fact] 18 | public void CanExtendCoverageWithMiddleDayOfPolicy() 19 | { 20 | var product = ProductsTestData.StandardCarInsurance(); 21 | var policy = PolicyTestData.StandardOneYearPolicy(new DateTime(2019, 1, 1)); 22 | 23 | var newCover = product.CoverWithCode("AC"); 24 | policy.ExtendCoverage 25 | ( 26 | new DateTime(2019, 7, 1), 27 | new CoverPrice(Guid.NewGuid(),newCover, Money.Euro(100) , TimeSpan.FromDays(365)) 28 | ); 29 | 30 | var resultingEvents = policy.GetUncommittedChanges(); 31 | 32 | //assert state 33 | policy 34 | .Should() 35 | .HaveVersions(2); 36 | 37 | policy.Versions.WithNumber(1).Should() 38 | .BeActive() 39 | .HaveActivePolicyStatus() 40 | .HaveTotalPremiumEqualTo(Money.Euro(500)); 41 | 42 | policy.Versions.WithNumber(2) 43 | .Should() 44 | .BeDraft() 45 | .HaveActivePolicyStatus() 46 | .CoverPeriod(new DateTime(2019, 1, 1), new DateTime(2020,1,1)) 47 | .HaveTotalPremiumEqualTo(Money.Euro(550.41)); 48 | 49 | 50 | //assert events 51 | resultingEvents 52 | .Should() 53 | .BeSingle() 54 | .ContainEvent( 55 | new CoverageExtendedPolicyVersionCreated 56 | ( 57 | 2, 58 | 1, 59 | ValidityPeriod.Between(new DateTime(2019,7,1), new DateTime(2020,1,1)), 60 | new PolicyCoverData("AC", new DateTime(2019,7,1), new DateTime(2020,1,1), Money.Euro(50.41), Money.Euro(100), TimeSpan.FromDays(365)) 61 | ) 62 | ); 63 | } 64 | 65 | [Fact] 66 | public void CanAddTheSameCoverTwice() 67 | { 68 | var product = ProductsTestData.StandardCarInsurance(); 69 | var policy = PolicyTestData.StandardOneYearPolicy(new DateTime(2019, 1, 1)); 70 | 71 | var doubledCover = product.CoverWithCode("OC"); 72 | var ex = Throws(() => policy.ExtendCoverage 73 | ( 74 | new DateTime(2019, 7, 1), 75 | new CoverPrice(Guid.NewGuid(),doubledCover, Money.Euro(100) , TimeSpan.FromDays(365)) 76 | )); 77 | Equal("This cover is already present", ex.Message); 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /SeparateModels/Controllers/PoliciesController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using MediatR; 3 | using Microsoft.AspNetCore.Mvc; 4 | using SeparateModels.Commands; 5 | using SeparateModels.Queries; 6 | using SeparateModels.Services; 7 | 8 | namespace SeparateModels.Controllers 9 | { 10 | [Route("api/[controller]")] 11 | [ApiController] 12 | public class PoliciesController : ControllerBase 13 | { 14 | private readonly IMediator mediator; 15 | 16 | public PoliciesController(IMediator mediator) 17 | { 18 | this.mediator = mediator; 19 | } 20 | 21 | [HttpPost] 22 | public async Task CreatePolicy([FromBody] CreatePolicyCommand cmd) 23 | { 24 | var result = await mediator.Send(cmd); 25 | return Ok(result); 26 | } 27 | 28 | [HttpPost("BuyAdditionalCover")] 29 | public async Task BuyAdditionalCover([FromBody] BuyAdditionalCoverCommand cmd) 30 | { 31 | var result = await mediator.Send(cmd); 32 | return Ok(result); 33 | } 34 | 35 | [HttpPost("ConfirmBuyAdditionalCover")] 36 | public async Task Post([FromBody] ConfirmBuyAdditionalCoverCommand cmd) 37 | { 38 | var result = await mediator.Send(cmd); 39 | return Ok(result); 40 | } 41 | 42 | [HttpPost("Terminate")] 43 | public async Task Terminate([FromBody] TerminatePolicyCommand cmd) 44 | { 45 | var result = await mediator.Send(cmd); 46 | return Ok(result); 47 | } 48 | 49 | [HttpPost("ConfirmTermination")] 50 | public async Task ConfirmTermination([FromBody] ConfirmTerminationCommand cmd) 51 | { 52 | var result = await mediator.Send(cmd); 53 | return Ok(result); 54 | } 55 | 56 | [HttpPost("CancelLastAnnex")] 57 | public async Task CancelLastAnnex([FromBody] CancelLastAnnexCommand cmd) 58 | { 59 | var result = await mediator.Send(cmd); 60 | return Ok(result); 61 | } 62 | 63 | [HttpGet("{policyNumber}/versions")] 64 | public async Task GetPolicyVersionsList(string policyNumber) 65 | { 66 | var result = await mediator.Send(new GetPolicyVersionsListQuery { PolicyNumber = policyNumber }); 67 | return Ok(result); 68 | } 69 | 70 | [HttpGet("{policyNumber}/versions/{versionNumber}")] 71 | public async Task GetPolicyVersionDetails(string policyNumber, int versionNumber) 72 | { 73 | var result = await mediator.Send(new GetPolicyVersionDetailsQuery { PolicyNumber = policyNumber, VersionNumber = versionNumber}); 74 | return Ok(result); 75 | } 76 | 77 | 78 | [HttpPost("find")] 79 | public async Task Find([FromBody] FindPoliciesQuery query) 80 | { 81 | var result = await mediator.Send(query); 82 | return Ok(result); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /CqrsWithEs.Tests/Asserts/PolicyAsserts.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CqrsWithEs.Domain.Policy; 3 | using NodaMoney; 4 | using static Xunit.Assert; 5 | 6 | namespace CqrsWithEs.Tests.Asserts 7 | { 8 | public class PolicyAssert 9 | { 10 | private readonly Policy policy; 11 | 12 | public PolicyAssert(Policy policy) 13 | { 14 | this.policy = policy; 15 | } 16 | 17 | public PolicyAssert HaveVersions(int expectedNumber) 18 | { 19 | Equal(expectedNumber, policy.Versions.Count); 20 | return this; 21 | } 22 | 23 | public PolicyVersionAssert VersionShould(int versionNumber) 24 | { 25 | return new PolicyVersionAssert(policy.Versions.WithNumber(versionNumber)); 26 | } 27 | } 28 | 29 | 30 | public class PolicyVersionAssert 31 | { 32 | private readonly PolicyVersion version; 33 | 34 | public PolicyVersionAssert(PolicyVersion version) 35 | { 36 | this.version = version; 37 | } 38 | 39 | public PolicyVersionAssert HaveActivePolicyStatus() 40 | { 41 | Equal(PolicyStatus.Active, version.PolicyStatus); 42 | return this; 43 | } 44 | 45 | public PolicyVersionAssert HaveTerminatedPolicyStatus() 46 | { 47 | Equal(PolicyStatus.Terminated, version.PolicyStatus); 48 | return this; 49 | } 50 | 51 | public PolicyVersionAssert BeDraft() 52 | { 53 | Equal(PolicyVersionStatus.Draft, version.VersionStatus); 54 | return this; 55 | } 56 | 57 | public PolicyVersionAssert BeActive() 58 | { 59 | Equal(PolicyVersionStatus.Active, version.VersionStatus); 60 | return this; 61 | } 62 | 63 | public PolicyVersionAssert HaveTotalPremiumEqualTo(Money amount) 64 | { 65 | Equal(amount, version.TotalPremium); 66 | return this; 67 | } 68 | 69 | public PolicyVersionAssert StartOn(DateTime theDate) 70 | { 71 | Equal(theDate, version.VersionPeriod.ValidFrom); 72 | return this; 73 | } 74 | 75 | public PolicyVersionAssert EndsOn(DateTime theDate) 76 | { 77 | Equal(theDate, version.VersionPeriod.ValidTo); 78 | return this; 79 | } 80 | 81 | public PolicyVersionAssert CoverPeriod(DateTime from, DateTime to) 82 | { 83 | Equal(from, version.CoverPeriod.ValidFrom); 84 | Equal(to, version.CoverPeriod.ValidTo); 85 | return this; 86 | } 87 | } 88 | 89 | public static class PolicyAssertsExtension 90 | { 91 | public static PolicyAssert Should(this Policy policy) 92 | { 93 | return new PolicyAssert(policy); 94 | } 95 | } 96 | 97 | public static class PolicyVersionAssertsExtension 98 | { 99 | public static PolicyVersionAssert Should(this PolicyVersion version) 100 | { 101 | return new PolicyVersionAssert(version); 102 | } 103 | } 104 | } -------------------------------------------------------------------------------- /CqrsWithEs/ReadModel/PolicyInfoDao.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CqrsWithEs.Domain.Policy; 3 | using Dapper; 4 | using Npgsql; 5 | 6 | namespace CqrsWithEs.ReadModel 7 | { 8 | public class PolicyInfoDao 9 | { 10 | private readonly string cnString; 11 | 12 | public PolicyInfoDao(string cnString) 13 | { 14 | this.cnString = cnString; 15 | } 16 | 17 | public void CreatePolicyInfo 18 | ( 19 | Guid id, 20 | string policyNumber, 21 | DateTime coverFrom, 22 | DateTime coverTo, 23 | string policyHolderFirstName, string policyHolderLastName, 24 | string carPlateNumber, string carMake, 25 | decimal totalPremium 26 | ) 27 | { 28 | using (var cn = new NpgsqlConnection(cnString)) 29 | { 30 | var policyInfo = new PolicyInfo 31 | { 32 | PolicyId = id, 33 | PolicyNumber = policyNumber, 34 | CoverFrom = coverFrom, 35 | CoverTo = coverTo, 36 | PolicyHolder = $"{policyHolderLastName} {policyHolderFirstName}", 37 | Vehicle = $"{carPlateNumber} {carMake}", 38 | TotalPremiumAmount = totalPremium 39 | }; 40 | 41 | cn.Open(); 42 | cn.Execute( 43 | "INSERT INTO public.policy_info_view (policy_id,policy_number,cover_from,cover_to,vehicle,policy_holder,total_premium) " + 44 | "VALUES (@PolicyId,@PolicyNumber,@CoverFrom,@CoverTo,@Vehicle,@PolicyHolder,@TotalPremiumAmount)", 45 | policyInfo); 46 | } 47 | } 48 | 49 | public void UpdatePolicyInfo 50 | ( 51 | Guid id, 52 | string policyNumber, 53 | DateTime coverFrom, 54 | DateTime coverTo, 55 | string policyHolderFirstName, string policyHolderLastName, 56 | string carPlateNumber, string carMake, 57 | decimal totalPremium 58 | ) 59 | { 60 | using (var cn = new NpgsqlConnection(cnString)) 61 | { 62 | var policyInfo = new PolicyInfo 63 | { 64 | PolicyId = id, 65 | CoverFrom = coverFrom, 66 | CoverTo = coverTo, 67 | PolicyHolder = $"{policyHolderLastName} {policyHolderFirstName}", 68 | Vehicle = $"{carPlateNumber} {carMake}", 69 | TotalPremiumAmount = totalPremium 70 | }; 71 | 72 | 73 | cn.Open(); 74 | cn.Execute( 75 | "UPDATE public.policy_info_view " + 76 | "SET " + 77 | "cover_from = @CoverFrom, " + 78 | "cover_to = @CoverTo, " + 79 | "vehicle = @Vehicle, " + 80 | "policy_holder = @PolicyHolder, " + 81 | "total_premium = @TotalPremiumAmount " + 82 | "WHERE policy_id = @PolicyId ", 83 | policyInfo); 84 | 85 | } 86 | } 87 | } 88 | } --------------------------------------------------------------------------------