├── .gitignore ├── LICENSE ├── README.md └── src ├── .nuget ├── NuGet.Config ├── NuGet.exe └── NuGet.targets ├── CQRSShop.Contracts ├── CQRSShop.Contracts.Helpers.fs ├── CQRSShop.Contracts.fsproj ├── Commands.fs ├── Events.fs └── Types.fs ├── CQRSShop.Domain.Tests ├── BasketTests │ ├── AddItemToBasketTest.cs │ ├── CheckoutBasketTests.cs │ ├── CreateBasketTests.cs │ ├── MakePaymentTests.cs │ └── ProceedCheckoutBasketTests.cs ├── CQRSShop.Domain.Tests.csproj ├── CustomerTests │ ├── CreateCustomerTest.cs │ └── MakeCustomerPreferredTest.cs ├── OrderTests │ └── AllTheOrderTests.cs ├── ProductTests │ └── CreateProductTests.cs ├── Properties │ └── AssemblyInfo.cs ├── TestBase.cs └── packages.config ├── CQRSShop.Domain ├── Aggregates │ ├── Basket.cs │ ├── Customer.cs │ ├── Order.cs │ └── Product.cs ├── CQRSShop.Domain.csproj ├── CommandHandlers │ ├── BasketCommandHandler.cs │ ├── CustomerCommandHandler.cs │ ├── OrderHandler.cs │ └── ProductCommandHandler.cs ├── DomainEntry.cs ├── Exceptions │ ├── CustomerAlreadyExistsException.cs │ └── DuplicateAggregateException.cs └── Properties │ └── AssemblyInfo.cs ├── CQRSShop.Infrastructure ├── AggregateBase.cs ├── CQRSShop.Infrastructure.csproj ├── CommandDispatcher.cs ├── DomainRepositoryBase.cs ├── EventStoreDomainRepository.cs ├── Exceptions │ └── AggregateNotFoundException.cs ├── IAggregate.cs ├── ICommand.cs ├── IDomainRepository.cs ├── IEvent.cs ├── IHandle.cs ├── IdGenerator.cs ├── InMemoryDomainRespository.cs ├── Properties │ └── AssemblyInfo.cs └── packages.config ├── CQRSShop.Search ├── CQRSShop.Search.csproj ├── Class1.cs └── Properties │ └── AssemblyInfo.cs ├── CQRSShop.Service ├── App.config ├── CQRSShop.Service.csproj ├── Documents │ ├── Basket.cs │ ├── Customer.cs │ └── Product.cs ├── EventSerialization.cs ├── EventStoreConnectionWrapper.cs ├── Indexer.cs ├── IndexingServie.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs └── packages.config ├── CQRSShop.Web ├── Api │ ├── BasePostEndpoint.cs │ ├── Basket │ │ ├── Checkout │ │ │ └── PostEndpoint.cs │ │ ├── Items │ │ │ └── PostEndpoint.cs │ │ ├── Pay │ │ │ └── PostEndpoint.cs │ │ ├── PostEndpoint.cs │ │ └── Proceed │ │ │ └── PostEndpoint.cs │ ├── Customer │ │ ├── PostEndpoint.cs │ │ └── Preferred │ │ │ └── PostEndpoint.cs │ ├── GetEndpoint.cs │ ├── Order │ │ ├── Approve │ │ │ └── PostEndpoint.cs │ │ ├── Cancel │ │ │ └── PostEndpoint.cs │ │ ├── Ship │ │ │ └── PostEndpoint.cs │ │ └── StartShipping │ │ │ └── PostEndpoint.cs │ └── Product │ │ └── PostEndpoint.cs ├── CQRSShop.Web.csproj ├── Configuration.cs ├── OwinAppSetup.cs ├── Properties │ └── AssemblyInfo.cs ├── Web.Debug.config ├── Web.Release.config ├── Web.config └── packages.config └── CQRSShop.sln /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | x64/ 20 | *_i.c 21 | *_p.c 22 | *.ilk 23 | *.meta 24 | *.obj 25 | *.pch 26 | *.pdb 27 | *.pgc 28 | *.pgd 29 | *.rsp 30 | *.sbr 31 | *.tlb 32 | *.tli 33 | *.tlh 34 | *.tmp 35 | *.log 36 | *.vspscc 37 | *.vssscc 38 | .builds 39 | 40 | # Visual C++ cache files 41 | ipch/ 42 | *.aps 43 | *.ncb 44 | *.opensdf 45 | *.sdf 46 | 47 | # Visual Studio profiler 48 | *.psess 49 | *.vsp 50 | *.vspx 51 | 52 | # Guidance Automation Toolkit 53 | *.gpState 54 | 55 | # ReSharper is a .NET coding add-in 56 | _ReSharper* 57 | 58 | # NCrunch 59 | *.ncrunch* 60 | .*crunch*.local.xml 61 | 62 | # Installshield output folder 63 | [Ee]xpress 64 | 65 | # DocProject is a documentation generator add-in 66 | DocProject/buildhelp/ 67 | DocProject/Help/*.HxT 68 | DocProject/Help/*.HxC 69 | DocProject/Help/*.hhc 70 | DocProject/Help/*.hhk 71 | DocProject/Help/*.hhp 72 | DocProject/Help/Html2 73 | DocProject/Help/html 74 | 75 | # Click-Once directory 76 | publish 77 | 78 | # Publish Web Output 79 | *.Publish.xml 80 | 81 | # NuGet Packages Directory 82 | packages 83 | 84 | # Windows Azure Build Output 85 | csx 86 | *.build.csdef 87 | 88 | # Windows Store app package directory 89 | AppPackages/ 90 | 91 | # Others 92 | [Bb]in 93 | [Oo]bj 94 | sql 95 | TestResults 96 | [Tt]est[Rr]esult* 97 | *.Cache 98 | ClientBin 99 | [Ss]tyle[Cc]op.* 100 | ~$* 101 | *.dbmdl 102 | Generated_Code #added for RIA/Silverlight projects 103 | 104 | # Backup & report files from converting an old project file to a newer 105 | # Visual Studio version. Backup files are not needed, because we have git ;-) 106 | _UpgradeReport_Files/ 107 | Backup*/ 108 | UpgradeLog*.XML 109 | 110 | src/Rapporteringsregisteret.Web/assets/less/*.css 111 | 112 | MetricResults/ 113 | *.sln.ide/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Tomas Jansson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | CQRSShop 2 | ======== 3 | 4 | Simple CQRS and eventsourcing with eventstore, elasticsearch and neo4j. I've written about it here: http://blog.tomasjansson.com/tag/cqrsshop/ 5 | 6 | For a pure functional version see: https://github.com/mastoj/FsCQRSShop 7 | -------------------------------------------------------------------------------- /src/.nuget/NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/.nuget/NuGet.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mastoj/CQRSShop/8af7e28e7b4ea386a41dc0b0e2f1448334d0e0a6/src/.nuget/NuGet.exe -------------------------------------------------------------------------------- /src/.nuget/NuGet.targets: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | $(MSBuildProjectDirectory)\..\ 5 | 6 | 7 | false 8 | 9 | 10 | false 11 | 12 | 13 | true 14 | 15 | 16 | false 17 | 18 | 19 | 20 | 21 | 22 | 26 | 27 | 28 | 29 | 30 | $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) 31 | 32 | 33 | 34 | 35 | $(SolutionDir).nuget 36 | 37 | 38 | 39 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName.Replace(' ', '_')).config 40 | $(MSBuildProjectDirectory)\packages.$(MSBuildProjectName).config 41 | 42 | 43 | 44 | $(MSBuildProjectDirectory)\packages.config 45 | $(PackagesProjectConfig) 46 | 47 | 48 | 49 | 50 | $(NuGetToolsPath)\NuGet.exe 51 | @(PackageSource) 52 | 53 | "$(NuGetExePath)" 54 | mono --runtime=v4.0.30319 "$(NuGetExePath)" 55 | 56 | $(TargetDir.Trim('\\')) 57 | 58 | -RequireConsent 59 | -NonInteractive 60 | 61 | "$(SolutionDir) " 62 | "$(SolutionDir)" 63 | 64 | 65 | $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(NonInteractiveSwitch) $(RequireConsentSwitch) -solutionDir $(PaddedSolutionDir) 66 | $(NuGetCommand) pack "$(ProjectPath)" -Properties "Configuration=$(Configuration);Platform=$(Platform)" $(NonInteractiveSwitch) -OutputDirectory "$(PackageOutputDir)" -symbols 67 | 68 | 69 | 70 | RestorePackages; 71 | $(BuildDependsOn); 72 | 73 | 74 | 75 | 76 | $(BuildDependsOn); 77 | BuildPackage; 78 | 79 | 80 | 81 | 82 | 83 | 84 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 100 | 103 | 104 | 105 | 106 | 108 | 109 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /src/CQRSShop.Contracts/CQRSShop.Contracts.Helpers.fs: -------------------------------------------------------------------------------- 1 | module CQRSShop.Contracts.Helpers 2 | 3 | let ToFSharpList x = List.ofSeq x 4 | 5 | -------------------------------------------------------------------------------- /src/CQRSShop.Contracts/CQRSShop.Contracts.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 2.0 8 | 66f74977-c96f-4af0-9f98-d2ca94749649 9 | Library 10 | CQRSShop.Contracts 11 | CQRSShop.Contracts 12 | v4.5.1 13 | 4.3.1.0 14 | CQRSShop.Contracts 15 | 16 | 17 | true 18 | full 19 | false 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | 3 24 | bin\Debug\CQRSShop.Contracts.XML 25 | 26 | 27 | pdbonly 28 | true 29 | true 30 | bin\Release\ 31 | TRACE 32 | 3 33 | bin\Release\CQRSShop.Contracts.XML 34 | 35 | 36 | 37 | 38 | True 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | CQRSShop.Infrastructure 53 | {57ae018a-ba49-471d-97d7-c4ad2040d4b0} 54 | True 55 | 56 | 57 | 58 | 11 59 | 60 | 61 | 62 | 63 | $(MSBuildExtensionsPath32)\..\Microsoft SDKs\F#\3.0\Framework\v4.0\Microsoft.FSharp.Targets 64 | 65 | 66 | 67 | 68 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\FSharp\Microsoft.FSharp.Targets 69 | 70 | 71 | 72 | 73 | 80 | -------------------------------------------------------------------------------- /src/CQRSShop.Contracts/Commands.fs: -------------------------------------------------------------------------------- 1 | namespace CQRSShop.Contracts.Commands 2 | open CQRSShop.Contracts.Types 3 | open CQRSShop.Infrastructure 4 | open System 5 | 6 | // Customer commands 7 | type CreateCustomer = {Id: Guid; Name: string } with interface ICommand 8 | type MarkCustomerAsPreferred = {Id: Guid; Discount: int } with interface ICommand 9 | 10 | // Product commands 11 | type CreateProduct = {Id: Guid; Name: string; Price: int } with interface ICommand 12 | 13 | // Basket commands 14 | type CreateBasket = { Id: Guid; CustomerId: Guid} with interface ICommand 15 | type AddItemToBasket = { Id: Guid; ProductId: Guid; Quantity: int } with interface ICommand 16 | type ProceedToCheckout = { Id: Guid } with interface ICommand 17 | type CheckoutBasket = { Id: Guid; ShippingAddress: Address } with interface ICommand 18 | type MakePayment = {Id: Guid; Payment: int } with interface ICommand 19 | 20 | // Order commands 21 | type StartShippingProcess = { Id: Guid } with interface ICommand 22 | type CancelOrder = { Id: Guid } with interface ICommand 23 | type ShipOrder = { Id: Guid } with interface ICommand 24 | type ApproveOrder = { Id: Guid } with interface ICommand 25 | -------------------------------------------------------------------------------- /src/CQRSShop.Contracts/Events.fs: -------------------------------------------------------------------------------- 1 | namespace CQRSShop.Contracts.Events 2 | open CQRSShop.Contracts.Types 3 | open CQRSShop.Infrastructure 4 | open System 5 | 6 | // Customer events 7 | type CustomerCreated = {Id: Guid; Name: string } 8 | with interface IEvent with member this.Id with get() = this.Id 9 | 10 | type CustomerMarkedAsPreferred = {Id: Guid; Discount: int } 11 | with interface IEvent with member this.Id with get() = this.Id 12 | 13 | // Product events 14 | type ProductCreated = {Id: Guid; Name: string; Price: int } 15 | with interface IEvent with member this.Id with get() = this.Id 16 | 17 | // Basket events 18 | type BasketCreated = { Id: Guid; CustomerId: Guid; Discount: int} 19 | with interface IEvent with member this.Id with get() = this.Id 20 | 21 | type ItemAdded = { Id: Guid; OrderLine: OrderLine} 22 | with 23 | override this.ToString() = sprintf "Item added. Id: %O, %O" this.Id this.OrderLine 24 | interface IEvent with member this.Id with get() = this.Id 25 | 26 | type CustomerIsCheckingOutBasket = { Id: Guid } 27 | with interface IEvent with member this.Id with get() = this.Id 28 | 29 | type BasketCheckedOut = { Id: Guid; ShippingAddress: Address } 30 | with interface IEvent with member this.Id with get() = this.Id 31 | 32 | // Order events 33 | type OrderCreated ={ Id: Guid; BasketId: Guid; OrderLines: OrderLine list } 34 | with interface IEvent with member this.Id with get() = this.Id 35 | 36 | type ShippingProcessStarted = {Id: Guid} 37 | with interface IEvent with member this.Id with get() = this.Id 38 | 39 | type OrderCancelled = {Id: Guid} 40 | with interface IEvent with member this.Id with get() = this.Id 41 | 42 | type OrderShipped = {Id: Guid} 43 | with interface IEvent with member this.Id with get() = this.Id 44 | 45 | type NeedsApproval = {Id: Guid} 46 | with interface IEvent with member this.Id with get() = this.Id 47 | 48 | type OrderApproved = {Id: Guid} 49 | with interface IEvent with member this.Id with get() = this.Id 50 | 51 | -------------------------------------------------------------------------------- /src/CQRSShop.Contracts/Types.fs: -------------------------------------------------------------------------------- 1 | namespace CQRSShop.Contracts.Types 2 | open System 3 | 4 | type Address = { Street: string } 5 | type OrderLine = {ProductId: Guid; ProductName: string; OriginalPrice: int; DiscountedPrice: int; Quantity: int} 6 | with override this.ToString() = sprintf "ProdcutName: %s, Price: %d, Discounted: %d, Quantity: %d" this.ProductName this.OriginalPrice this.DiscountedPrice this.Quantity 7 | -------------------------------------------------------------------------------- /src/CQRSShop.Domain.Tests/BasketTests/AddItemToBasketTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CQRSShop.Contracts.Commands; 3 | using CQRSShop.Contracts.Events; 4 | using CQRSShop.Contracts.Types; 5 | using CQRSShop.Tests; 6 | using NUnit.Framework; 7 | 8 | namespace CQRSShop.Domain.Tests.BasketTests 9 | { 10 | [TestFixture] 11 | public class AddItemToBasketTest : TestBase 12 | { 13 | [TestCase("NameA", 100, 10)] 14 | [TestCase("NameB", 200, 20)] 15 | public void GivenWeHaveABasketForARegularCustomer_WhenAddingItems_ThePriceOfTheBasketShouldNotBeDiscounted(string productName, int itemPrice, int quantity) 16 | { 17 | var customerId = Guid.NewGuid(); 18 | var productId = Guid.NewGuid(); 19 | var id = Guid.NewGuid(); 20 | var expectedOrderLine = new OrderLine(productId, productName, itemPrice, itemPrice, quantity); 21 | Given(new ProductCreated(productId, productName, itemPrice), 22 | new BasketCreated(id, customerId, 0)); 23 | When(new AddItemToBasket(id, productId, quantity)); 24 | Then(new ItemAdded(id, expectedOrderLine)); 25 | } 26 | 27 | [TestCase("NameA", 100, 10, 10, 90)] 28 | [TestCase("NameB", 200, 20, 80, 40)] 29 | public void GivenWeHaveABasketForAPreferredCustomer_WhenAddingItems_ThePriceOfTheBasketShouldBeDiscounted(string productName, int itemPrice, int quantity, int discountPercentage, int discountedPrice) 30 | { 31 | var customerId = Guid.NewGuid(); 32 | var productId = Guid.NewGuid(); 33 | var id = Guid.NewGuid(); 34 | var expectedOrderLine = new OrderLine(productId, productName, itemPrice, discountedPrice, quantity); 35 | Given(new CustomerCreated(customerId, "John Doe"), 36 | new CustomerMarkedAsPreferred(customerId, discountPercentage), 37 | new ProductCreated(productId, productName, itemPrice), 38 | new BasketCreated(id, customerId, discountPercentage)); 39 | When(new AddItemToBasket(id, productId, quantity)); 40 | Then(new ItemAdded(id, expectedOrderLine)); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain.Tests/BasketTests/CheckoutBasketTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CQRSShop.Contracts.Commands; 3 | using CQRSShop.Contracts.Events; 4 | using CQRSShop.Contracts.Types; 5 | using CQRSShop.Domain.Exceptions; 6 | using CQRSShop.Tests; 7 | using NUnit.Framework; 8 | 9 | namespace CQRSShop.Domain.Tests.BasketTests 10 | { 11 | [TestFixture] 12 | public class CheckoutBasketTests : TestBase 13 | { 14 | [TestCase(null)] 15 | [TestCase("")] 16 | [TestCase(" ")] 17 | public void WhenTheUserCheckoutWithInvalidAddress_IShouldGetNotified(string street) 18 | { 19 | var address = street == null ? null : new Address(street); 20 | var id = Guid.NewGuid(); 21 | Given(new BasketCreated(id, Guid.NewGuid(), 0)); 22 | WhenThrows(new CheckoutBasket(id, address)); 23 | } 24 | 25 | [Test] 26 | public void WhenTheUserCheckoutWithAValidAddress_IShouldProceedToTheNextStep() 27 | { 28 | var address = new Address("Valid street"); 29 | var id = Guid.NewGuid(); 30 | Given(new BasketCreated(id, Guid.NewGuid(), 0)); 31 | When(new CheckoutBasket(id, address)); 32 | Then(new BasketCheckedOut(id, address)); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain.Tests/BasketTests/CreateBasketTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CQRSShop.Contracts.Commands; 3 | using CQRSShop.Contracts.Events; 4 | using CQRSShop.Domain.Exceptions; 5 | using CQRSShop.Infrastructure.Exceptions; 6 | using CQRSShop.Tests; 7 | using NUnit.Framework; 8 | 9 | namespace CQRSShop.Domain.Tests.BasketTests 10 | { 11 | [TestFixture] 12 | public class CreateBasketTests : TestBase 13 | { 14 | [Test] 15 | public void GivenCustomerWithIdXExists_WhenCreatingABasketForCustomerX_ThenTheBasketShouldBeCreated() 16 | { 17 | var id = Guid.NewGuid(); 18 | var customerId = Guid.NewGuid(); 19 | int discount = 0; 20 | string name = "John doe"; 21 | Given(new CustomerCreated(customerId, name)); 22 | When(new CreateBasket(id, customerId)); 23 | Then(new BasketCreated(id, customerId, discount)); 24 | } 25 | 26 | [Test] 27 | public void GivenNoCustomerWithIdXExists_WhenCreatingABasketForCustomerX_IShouldGetNotified() 28 | { 29 | var id = Guid.NewGuid(); 30 | var customerId = Guid.NewGuid(); 31 | WhenThrows(new CreateBasket(id, customerId)); 32 | } 33 | 34 | [Test] 35 | public void GivenCustomerWithIdXExistsAndBasketAlreadyExistsForIdY_WhenCreatingABasketForCustomerXAndIdY_IShouldGetNotified() 36 | { 37 | var id = Guid.NewGuid(); 38 | var customerId = Guid.NewGuid(); 39 | string name = "John doe"; 40 | int discount = 0; 41 | Given(new BasketCreated(id, Guid.NewGuid(), discount), 42 | new CustomerCreated(customerId, name)); 43 | WhenThrows(new CreateBasket(id, customerId)); 44 | } 45 | 46 | [Test] 47 | public void GivenACustomerWithADiscount_CreatingABasketForTheCustomer_TheDiscountShouldBeIncluded() 48 | { 49 | var id = Guid.NewGuid(); 50 | var customerId = Guid.NewGuid(); 51 | int discount = 89; 52 | string name = "John doe"; 53 | Given(new CustomerCreated(customerId, name), 54 | new CustomerMarkedAsPreferred(customerId, discount)); 55 | When(new CreateBasket(id, customerId)); 56 | Then(new BasketCreated(id, customerId, discount)); 57 | } 58 | } 59 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain.Tests/BasketTests/MakePaymentTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CQRSShop.Contracts; 3 | using CQRSShop.Contracts.Commands; 4 | using CQRSShop.Contracts.Events; 5 | using CQRSShop.Contracts.Types; 6 | using CQRSShop.Domain.Exceptions; 7 | using CQRSShop.Infrastructure; 8 | using CQRSShop.Tests; 9 | using Microsoft.FSharp.Collections; 10 | using NUnit.Framework; 11 | 12 | namespace CQRSShop.Domain.Tests.BasketTests 13 | { 14 | [TestFixture] 15 | public class MakePaymentTests : TestBase 16 | { 17 | [TestCase(100, 101)] 18 | [TestCase(100, 99)] 19 | [TestCase(100, 91)] 20 | [TestCase(100, 89)] 21 | public void WhenNotPayingTheExpectedAmount_IShouldGetNotified(int productPrice, int payment) 22 | { 23 | var id = Guid.NewGuid(); 24 | var existingOrderLine = new OrderLine(Guid.NewGuid(), "", productPrice, productPrice, 1); 25 | Given(new BasketCreated(id, Guid.NewGuid(), 0), 26 | new ItemAdded(id, existingOrderLine)); 27 | WhenThrows(new MakePayment(id, payment)); 28 | } 29 | 30 | [TestCase(100, 101, 101)] 31 | [TestCase(100, 80, 80)] 32 | public void WhenPayingTheExpectedAmount_ThenANewOrderShouldBeCreatedFromTheResult(int productPrice, int discountPrice, int payment) 33 | { 34 | var id = Guid.NewGuid(); 35 | int dontCare = 0; 36 | var orderId = Guid.NewGuid(); 37 | IdGenerator.GenerateGuid = () => orderId; 38 | var existingOrderLine = new OrderLine(Guid.NewGuid(), "Ball", productPrice, discountPrice, 1); 39 | Given(new BasketCreated(id, Guid.NewGuid(), dontCare), 40 | new ItemAdded(id, existingOrderLine)); 41 | When(new MakePayment(id, payment)); 42 | 43 | var items = Helpers.ToFSharpList(new[] {existingOrderLine}); 44 | Then(new OrderCreated(orderId, id, items), 45 | new OrderApproved(orderId)); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain.Tests/BasketTests/ProceedCheckoutBasketTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CQRSShop.Contracts.Commands; 3 | using CQRSShop.Contracts.Events; 4 | using CQRSShop.Tests; 5 | using NUnit.Framework; 6 | 7 | namespace CQRSShop.Domain.Tests.BasketTests 8 | { 9 | [TestFixture] 10 | public class ProceedCheckoutBasketTests : TestBase 11 | { 12 | [Test] 13 | public void GivenABasket_WhenCreatingABasketForCustomerX_ThenTheBasketShouldBeCreated() 14 | { 15 | var id = Guid.NewGuid(); 16 | var customerId = Guid.NewGuid(); 17 | int discount = 0; 18 | Given(new BasketCreated(id, customerId, discount)); 19 | When(new ProceedToCheckout(id)); 20 | Then(new CustomerIsCheckingOutBasket(id)); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain.Tests/CQRSShop.Domain.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {737BFB25-3617-437E-90D6-C15ECAD8FB25} 8 | Library 9 | Properties 10 | CQRSShop.Domain.Tests 11 | CQRSShop.Domain.Tests 12 | v4.5.1 13 | 512 14 | ..\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | ..\packages\NUnit.2.6.3\lib\nunit.framework.dll 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | {66f74977-c96f-4af0-9f98-d2ca94749649} 66 | CQRSShop.Contracts 67 | 68 | 69 | {00afe5da-93ef-4c5a-be98-006ecf0e42e6} 70 | CQRSShop.Domain 71 | 72 | 73 | {57ae018a-ba49-471d-97d7-c4ad2040d4b0} 74 | CQRSShop.Infrastructure 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 83 | 84 | 85 | 86 | 93 | -------------------------------------------------------------------------------- /src/CQRSShop.Domain.Tests/CustomerTests/CreateCustomerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CQRSShop.Contracts.Commands; 3 | using CQRSShop.Contracts.Events; 4 | using CQRSShop.Domain.Exceptions; 5 | using CQRSShop.Tests; 6 | using NUnit.Framework; 7 | 8 | namespace CQRSShop.Domain.Tests.CustomerTests 9 | { 10 | [TestFixture] 11 | public class CreateCustomerTest : TestBase 12 | { 13 | [Test] 14 | public void WhenCreatingTheCustomer_TheCustomerShouldBeCreatedWithTheRightName() 15 | { 16 | Guid id = Guid.NewGuid(); 17 | When(new CreateCustomer(id, "Tomas")); 18 | Then(new CustomerCreated(id, "Tomas")); 19 | } 20 | 21 | [Test] 22 | public void GivenAUserWithIdXExists_WhenCreatingACustomerWithIdX_IShouldGetNotifiedThatTheUserAlreadyExists() 23 | { 24 | Guid id = Guid.NewGuid(); 25 | Given(new CustomerCreated(id, "Something I don't care about")); 26 | WhenThrows(new CreateCustomer(id, "Tomas")); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain.Tests/CustomerTests/MakeCustomerPreferredTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CQRSShop.Contracts.Commands; 3 | using CQRSShop.Contracts.Events; 4 | using CQRSShop.Tests; 5 | using NUnit.Framework; 6 | 7 | namespace CQRSShop.Domain.Tests.CustomerTests 8 | { 9 | [TestFixture] 10 | public class MarkCustomerAsPreferredTest : TestBase 11 | { 12 | [TestCase(25)] 13 | [TestCase(50)] 14 | [TestCase(70)] 15 | public void GivenTheUserExists_WhenMarkingCustomerAsPreferred_ThenTheCustomerShouldBePreferred(int discount) 16 | { 17 | Guid id = Guid.NewGuid(); 18 | Given(new CustomerCreated(id, "Superman")); 19 | When(new MarkCustomerAsPreferred(id, discount)); 20 | Then(new CustomerMarkedAsPreferred(id, discount)); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain.Tests/OrderTests/AllTheOrderTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CQRSShop.Contracts.Commands; 3 | using CQRSShop.Contracts.Events; 4 | using CQRSShop.Contracts.Types; 5 | using CQRSShop.Domain.Exceptions; 6 | using CQRSShop.Infrastructure; 7 | using CQRSShop.Tests; 8 | using Microsoft.FSharp.Collections; 9 | using CQRSShop.Contracts; 10 | using NUnit.Framework; 11 | 12 | namespace CQRSShop.Domain.Tests.OrderTests 13 | { 14 | [TestFixture] 15 | public class AllTheOrderTests : TestBase 16 | { 17 | [Test] 18 | public void WhenStartingShippingProcess_TheShippingShouldBeStarted() 19 | { 20 | var id = Guid.NewGuid(); 21 | var orderCreated = BuildOrderCreated(id, basketId: Guid.NewGuid(), numberOfOrderLines: 1); 22 | Given(orderCreated); 23 | When(new StartShippingProcess(id)); 24 | Then(new ShippingProcessStarted(id)); 25 | } 26 | 27 | [Test] 28 | public void WhenCancellingAnOrderThatHasntBeenStartedShipping_TheOrderShouldBeCancelled() 29 | { 30 | var id = Guid.NewGuid(); 31 | var orderCreated = BuildOrderCreated(id, basketId: Guid.NewGuid(), numberOfOrderLines: 1); 32 | Given(orderCreated); 33 | When(new CancelOrder(id)); 34 | Then(new OrderCancelled(id)); 35 | } 36 | 37 | [Test] 38 | public void WhenTryingToStartShippingACancelledOrder_IShouldBeNotified() 39 | { 40 | var id = Guid.NewGuid(); 41 | var orderCreated = BuildOrderCreated(id, basketId: Guid.NewGuid(), numberOfOrderLines: 1); 42 | Given(orderCreated, 43 | new OrderCancelled(id)); 44 | WhenThrows(new StartShippingProcess(id)); 45 | } 46 | 47 | [Test] 48 | public void WhenTryingToCancelAnOrderThatIsAboutToShip_IShouldBeNotified() 49 | { 50 | var id = Guid.NewGuid(); 51 | var orderCreated = BuildOrderCreated(id, basketId: Guid.NewGuid(), numberOfOrderLines: 1); 52 | Given(orderCreated, 53 | new ShippingProcessStarted(id)); 54 | WhenThrows(new CancelOrder(id)); 55 | } 56 | 57 | [Test] 58 | public void WhenShippingAnOrderThatTheShippingProcessIsStarted_ItShouldBeMarkedAsShipped() 59 | { 60 | var id = Guid.NewGuid(); 61 | var orderCreated = BuildOrderCreated(id, basketId: Guid.NewGuid(), numberOfOrderLines: 1); 62 | Given(orderCreated, 63 | new ShippingProcessStarted(id)); 64 | When(new ShipOrder(id)); 65 | Then(new OrderShipped(id)); 66 | } 67 | 68 | [Test] 69 | public void WhenShippingAnOrderWhereShippingIsNotStarted_IShouldGetNotified() 70 | { 71 | var id = Guid.NewGuid(); 72 | var orderCreated = BuildOrderCreated(id, basketId: Guid.NewGuid(), numberOfOrderLines: 1); 73 | Given(orderCreated); 74 | WhenThrows(new ShipOrder(id)); 75 | } 76 | 77 | [Test] 78 | public void WhenTheUserCheckoutWithAnAmountLargerThan100000_TheOrderNeedsApproval() 79 | { 80 | var address = new Address("Valid street"); 81 | var basketId = Guid.NewGuid(); 82 | var orderId = Guid.NewGuid(); 83 | IdGenerator.GenerateGuid = () => orderId; 84 | var orderLine = new OrderLine(Guid.NewGuid(), "Ball", 100000, 100001, 1); 85 | Given(new BasketCreated(basketId, Guid.NewGuid(), 0), 86 | new ItemAdded(basketId, orderLine), 87 | new BasketCheckedOut(basketId, address)); 88 | When(new MakePayment(basketId, 100001)); 89 | Then(new OrderCreated(orderId, basketId, Helpers.ToFSharpList(new [] {orderLine})), 90 | new NeedsApproval(orderId)); 91 | } 92 | 93 | [Test] 94 | public void WhenTheUserCheckoutWithAnAmountLessThan100000_TheOrderIsAutomaticallyApproved() 95 | { 96 | var address = new Address("Valid street"); 97 | var basketId = Guid.NewGuid(); 98 | var orderId = Guid.NewGuid(); 99 | IdGenerator.GenerateGuid = () => orderId; 100 | var orderLine = new OrderLine(Guid.NewGuid(), "Ball", 100000, 100000, 1); 101 | Given(new BasketCreated(basketId, Guid.NewGuid(), 0), 102 | new ItemAdded(basketId, orderLine), 103 | new BasketCheckedOut(basketId, address)); 104 | When(new MakePayment(basketId, 100000)); 105 | Then(new OrderCreated(orderId, basketId, Helpers.ToFSharpList(new[] { orderLine })), 106 | new OrderApproved(orderId)); 107 | } 108 | 109 | [Test] 110 | public void WhenApprovingAnOrder_ItShouldBeApproved() 111 | { 112 | var orderId = Guid.NewGuid(); 113 | Given(new OrderCreated(orderId, Guid.NewGuid(), FSharpList.Empty)); 114 | When(new ApproveOrder(orderId)); 115 | Then(new OrderApproved(orderId)); 116 | } 117 | 118 | private OrderCreated BuildOrderCreated(Guid orderId, Guid basketId, int numberOfOrderLines, int pricePerProduct = 100) 119 | { 120 | var orderLines = FSharpList.Empty; 121 | for (var i = 0; i < numberOfOrderLines; i++) 122 | { 123 | orderLines = FSharpList.Cons(new OrderLine(Guid.NewGuid(), "Line " + i, pricePerProduct, pricePerProduct, 1), orderLines); 124 | } 125 | return new OrderCreated(orderId, basketId, orderLines); 126 | } 127 | } 128 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain.Tests/ProductTests/CreateProductTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CQRSShop.Contracts.Commands; 3 | using CQRSShop.Contracts.Events; 4 | using CQRSShop.Domain.Exceptions; 5 | using CQRSShop.Tests; 6 | using NUnit.Framework; 7 | 8 | namespace CQRSShop.Domain.Tests.ProductTests 9 | { 10 | [TestFixture] 11 | public class CreateProductTests : TestBase 12 | { 13 | [TestCase("ball", 1000)] 14 | [TestCase("train", 10000)] 15 | [TestCase("universe", 999999)] 16 | public void WhenCreatingAProduct_TheProductShouldBeCreatedWithTheCorrectPrice(string productName, int price) 17 | { 18 | Guid id = Guid.NewGuid(); 19 | When(new CreateProduct(id, productName, price)); 20 | Then(new ProductCreated(id, productName, price)); 21 | } 22 | 23 | [Test] 24 | public void GivenProductXExists_WhenCreatingAProductWithIdX_IShouldGetNotifiedThatTheProductAlreadyExists() 25 | { 26 | Guid id = Guid.NewGuid(); 27 | Given(new ProductCreated(id, "Something I don't care about", 9999)); 28 | WhenThrows(new CreateProduct(id, "Sugar", 999)); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CQRSShop.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CQRSShop.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("10e8c961-d47d-4ed2-87eb-d6b49ff24faa")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/CQRSShop.Domain.Tests/TestBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CQRSShop.Domain; 5 | using CQRSShop.Infrastructure; 6 | using NUnit.Framework; 7 | 8 | namespace CQRSShop.Tests 9 | { 10 | public class TestBase 11 | { 12 | private InMemoryDomainRespository _domainRepository; 13 | private DomainEntry _domainEntry; 14 | private Dictionary> _preConditions = new Dictionary>(); 15 | 16 | private DomainEntry BuildApplication() 17 | { 18 | _domainRepository = new InMemoryDomainRespository(); 19 | _domainRepository.AddEvents(_preConditions); 20 | return new DomainEntry(_domainRepository); 21 | } 22 | 23 | [TestFixtureTearDown] 24 | public void TearDown() 25 | { 26 | IdGenerator.GenerateGuid = null; 27 | _preConditions = new Dictionary>(); 28 | } 29 | 30 | protected void When(ICommand command) 31 | { 32 | var application = BuildApplication(); 33 | application.ExecuteCommand(command); 34 | } 35 | 36 | protected void Then(params IEvent[] expectedEvents) 37 | { 38 | var latestEvents = _domainRepository.GetLatestEvents().ToList(); 39 | var expectedEventsList = expectedEvents.ToList(); 40 | Assert.AreEqual(expectedEventsList.Count, latestEvents.Count); 41 | 42 | for (int i = 0; i < latestEvents.Count; i++) 43 | { 44 | Assert.AreEqual(expectedEvents[i], latestEvents[i]); 45 | } 46 | } 47 | 48 | protected void WhenThrows(ICommand command) where TException : Exception 49 | { 50 | try 51 | { 52 | When(command); 53 | Assert.Fail("Expected exception " + typeof(TException)); 54 | } 55 | catch (TException) 56 | { 57 | } 58 | } 59 | 60 | protected void Given(params IEvent[] existingEvents) 61 | { 62 | _preConditions = existingEvents 63 | .GroupBy(y => y.Id) 64 | .ToDictionary(y => y.Key, y => y.AsEnumerable()); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/CQRSShop.Domain.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/CQRSShop.Domain/Aggregates/Basket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CQRSShop.Contracts.Events; 5 | using CQRSShop.Contracts.Types; 6 | using CQRSShop.Domain.Exceptions; 7 | using CQRSShop.Infrastructure; 8 | using Microsoft.FSharp.Collections; 9 | 10 | namespace CQRSShop.Domain.Aggregates 11 | { 12 | internal class Basket : AggregateBase 13 | { 14 | private int _discount; 15 | private FSharpList _orderLines; 16 | 17 | private Basket(Guid id, Guid customerId, int discount) : this() 18 | { 19 | RaiseEvent(new BasketCreated(id, customerId, discount)); 20 | } 21 | 22 | public Basket() 23 | { 24 | RegisterTransition(Apply); 25 | RegisterTransition(Apply); 26 | } 27 | 28 | private void Apply(ItemAdded obj) 29 | { 30 | _orderLines = FSharpList.Cons(obj.OrderLine, _orderLines); 31 | } 32 | 33 | private void Apply(BasketCreated obj) 34 | { 35 | Id = obj.Id; 36 | _discount = obj.Discount; 37 | _orderLines = FSharpList.Empty; 38 | } 39 | 40 | internal static IAggregate Create(Guid id, Customer customer) 41 | { 42 | return new Basket(id, customer.Id, customer.Discount); 43 | } 44 | 45 | internal void AddItem(Product product, int quantity) 46 | { 47 | var discount = (int)(product.Price * ((double)_discount/100)); 48 | var discountedPrice = product.Price - discount; 49 | var orderLine = new OrderLine(product.Id, product.Name, product.Price, discountedPrice, quantity); 50 | RaiseEvent(new ItemAdded(Id, orderLine)); 51 | } 52 | 53 | internal void ProceedToCheckout() 54 | { 55 | RaiseEvent(new CustomerIsCheckingOutBasket(Id)); 56 | } 57 | 58 | internal void Checkout(Address shippingAddress) 59 | { 60 | if(shippingAddress == null || string.IsNullOrWhiteSpace(shippingAddress.Street)) 61 | throw new MissingAddressException(); 62 | RaiseEvent(new BasketCheckedOut(Id, shippingAddress)); 63 | } 64 | 65 | internal IAggregate MakePayment(int payment) 66 | { 67 | var expectedPayment = _orderLines.Sum(y => y.DiscountedPrice * y.Quantity); 68 | if(expectedPayment != payment) 69 | throw new UnexpectedPaymentException(); 70 | return new Order(Id, _orderLines); 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain/Aggregates/Customer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CQRSShop.Contracts.Events; 3 | using CQRSShop.Infrastructure; 4 | 5 | namespace CQRSShop.Domain.Aggregates 6 | { 7 | internal class Customer : AggregateBase 8 | { 9 | public Customer() 10 | { 11 | RegisterTransition(Apply); 12 | RegisterTransition(Apply); 13 | } 14 | 15 | private Customer(Guid id, string name) : this() 16 | { 17 | RaiseEvent(new CustomerCreated(id, name)); 18 | } 19 | 20 | internal int Discount { get; set; } 21 | 22 | private void Apply(CustomerCreated obj) 23 | { 24 | Id = obj.Id; 25 | } 26 | 27 | private void Apply(CustomerMarkedAsPreferred obj) 28 | { 29 | Discount = obj.Discount; 30 | } 31 | 32 | internal static IAggregate Create(Guid id, string name) 33 | { 34 | return new Customer(id, name); 35 | } 36 | 37 | internal void MakePreferred(int discount) 38 | { 39 | RaiseEvent(new CustomerMarkedAsPreferred(Id, discount)); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain/Aggregates/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using CQRSShop.Contracts.Events; 4 | using CQRSShop.Contracts.Types; 5 | using CQRSShop.Domain.Exceptions; 6 | using CQRSShop.Infrastructure; 7 | using Microsoft.FSharp.Collections; 8 | 9 | namespace CQRSShop.Domain.Aggregates 10 | { 11 | internal class Order : AggregateBase 12 | { 13 | private OrderState _orderState; 14 | 15 | private enum OrderState 16 | { 17 | ShippingProcessStarted, 18 | Created, 19 | Cancelled 20 | } 21 | 22 | public Order() 23 | { 24 | RegisterTransition(Apply); 25 | RegisterTransition(Apply); 26 | RegisterTransition(Apply); 27 | } 28 | 29 | private void Apply(OrderCancelled obj) 30 | { 31 | _orderState = OrderState.Cancelled; 32 | } 33 | 34 | private void Apply(ShippingProcessStarted obj) 35 | { 36 | _orderState = OrderState.ShippingProcessStarted; 37 | } 38 | 39 | private void Apply(OrderCreated obj) 40 | { 41 | _orderState = OrderState.Created; 42 | Id = obj.Id; 43 | } 44 | 45 | internal Order(Guid basketId, FSharpList orderLines) : this() 46 | { 47 | var id = IdGenerator.GenerateGuid(); 48 | RaiseEvent(new OrderCreated(id, basketId, orderLines)); 49 | var totalPrice = orderLines.Sum(y => y.DiscountedPrice); 50 | if (totalPrice > 100000) 51 | { 52 | RaiseEvent(new NeedsApproval(id)); 53 | } 54 | else 55 | { 56 | RaiseEvent(new OrderApproved(id)); 57 | } 58 | } 59 | 60 | internal void Approve() 61 | { 62 | RaiseEvent(new OrderApproved(Id)); 63 | } 64 | 65 | internal void StartShippingProcess() 66 | { 67 | if (_orderState == OrderState.Cancelled) 68 | throw new OrderCancelledException(); 69 | 70 | RaiseEvent(new ShippingProcessStarted(Id)); 71 | } 72 | 73 | internal void Cancel() 74 | { 75 | if (_orderState == OrderState.Created) 76 | { 77 | RaiseEvent(new OrderCancelled(Id)); 78 | } 79 | else 80 | { 81 | throw new ShippingStartedException(); 82 | } 83 | } 84 | 85 | internal void ShipOrder() 86 | { 87 | if (_orderState != OrderState.ShippingProcessStarted) 88 | throw new InvalidOrderState(); 89 | RaiseEvent(new OrderShipped(Id)); 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain/Aggregates/Product.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CQRSShop.Contracts.Events; 3 | using CQRSShop.Infrastructure; 4 | 5 | namespace CQRSShop.Domain.Aggregates 6 | { 7 | internal class Product : AggregateBase 8 | { 9 | public Product() 10 | { 11 | RegisterTransition(Apply); 12 | } 13 | 14 | internal string Name { get; private set; } 15 | internal int Price { get; private set; } 16 | 17 | private void Apply(ProductCreated obj) 18 | { 19 | Id = obj.Id; 20 | Name = obj.Name; 21 | Price = obj.Price; 22 | } 23 | 24 | private Product(Guid id, string name, int price) : this() 25 | { 26 | RaiseEvent(new ProductCreated(id, name, price)); 27 | } 28 | 29 | internal static IAggregate Create(Guid id, string name, int price) 30 | { 31 | return new Product(id, name, price); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain/CQRSShop.Domain.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {00AFE5DA-93EF-4C5A-BE98-006ECF0E42E6} 8 | Library 9 | Properties 10 | CQRSShop.Domain 11 | CQRSShop.Domain 12 | v4.5.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | {66f74977-c96f-4af0-9f98-d2ca94749649} 59 | CQRSShop.Contracts 60 | 61 | 62 | {57ae018a-ba49-471d-97d7-c4ad2040d4b0} 63 | CQRSShop.Infrastructure 64 | 65 | 66 | 67 | 74 | -------------------------------------------------------------------------------- /src/CQRSShop.Domain/CommandHandlers/BasketCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using CQRSShop.Contracts.Commands; 2 | using CQRSShop.Domain.Aggregates; 3 | using CQRSShop.Domain.Exceptions; 4 | using CQRSShop.Infrastructure; 5 | using CQRSShop.Infrastructure.Exceptions; 6 | 7 | namespace CQRSShop.Domain.CommandHandlers 8 | { 9 | internal class BasketCommandHandler : 10 | IHandle, 11 | IHandle, 12 | IHandle, 13 | IHandle, 14 | IHandle 15 | { 16 | private readonly IDomainRepository _domainRepository; 17 | 18 | public BasketCommandHandler(IDomainRepository domainRepository) 19 | { 20 | _domainRepository = domainRepository; 21 | } 22 | 23 | public IAggregate Handle(CreateBasket command) 24 | { 25 | try 26 | { 27 | var basket = _domainRepository.GetById(command.Id); 28 | throw new BasketAlreadExistsException(command.Id); 29 | } 30 | catch (AggregateNotFoundException) 31 | { 32 | //Expect this 33 | } 34 | var customer = _domainRepository.GetById(command.CustomerId); 35 | return Basket.Create(command.Id, customer); 36 | } 37 | 38 | public IAggregate Handle(AddItemToBasket command) 39 | { 40 | var basket = _domainRepository.GetById(command.Id); 41 | var product = _domainRepository.GetById(command.ProductId); 42 | basket.AddItem(product, command.Quantity); 43 | return basket; 44 | } 45 | 46 | public IAggregate Handle(ProceedToCheckout command) 47 | { 48 | var basket = _domainRepository.GetById(command.Id); 49 | basket.ProceedToCheckout(); 50 | return basket; 51 | } 52 | 53 | public IAggregate Handle(CheckoutBasket command) 54 | { 55 | var basket = _domainRepository.GetById(command.Id); 56 | basket.Checkout(command.ShippingAddress); 57 | return basket; 58 | } 59 | 60 | public IAggregate Handle(MakePayment command) 61 | { 62 | var basket = _domainRepository.GetById(command.Id); 63 | var order = basket.MakePayment(command.Payment); 64 | return order; 65 | } 66 | } 67 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain/CommandHandlers/CustomerCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CQRSShop.Contracts.Commands; 3 | using CQRSShop.Domain.Aggregates; 4 | using CQRSShop.Domain.Exceptions; 5 | using CQRSShop.Infrastructure; 6 | using CQRSShop.Infrastructure.Exceptions; 7 | 8 | namespace CQRSShop.Domain.CommandHandlers 9 | { 10 | internal class CustomerCommandHandler : 11 | IHandle, 12 | IHandle 13 | { 14 | private readonly IDomainRepository _domainRepository; 15 | 16 | public CustomerCommandHandler(IDomainRepository domainRepository) 17 | { 18 | _domainRepository = domainRepository; 19 | } 20 | 21 | public IAggregate Handle(CreateCustomer command) 22 | { 23 | try 24 | { 25 | var customer = _domainRepository.GetById(command.Id); 26 | throw new CustomerAlreadyExistsException(command.Id); 27 | } 28 | catch (AggregateNotFoundException) 29 | { 30 | // We expect not to find anything 31 | } 32 | return Customer.Create(command.Id, command.Name); 33 | } 34 | 35 | public IAggregate Handle(MarkCustomerAsPreferred command) 36 | { 37 | var customer = _domainRepository.GetById(command.Id); 38 | customer.MakePreferred(command.Discount); 39 | return customer; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain/CommandHandlers/OrderHandler.cs: -------------------------------------------------------------------------------- 1 | using CQRSShop.Contracts.Commands; 2 | using CQRSShop.Domain.Aggregates; 3 | using CQRSShop.Infrastructure; 4 | 5 | namespace CQRSShop.Domain.CommandHandlers 6 | { 7 | internal class OrderHandler : 8 | IHandle, 9 | IHandle, 10 | IHandle, 11 | IHandle 12 | { 13 | private readonly IDomainRepository _domainRepository; 14 | 15 | public OrderHandler(IDomainRepository domainRepository) 16 | { 17 | _domainRepository = domainRepository; 18 | } 19 | 20 | public IAggregate Handle(ApproveOrder command) 21 | { 22 | var order = _domainRepository.GetById(command.Id); 23 | order.Approve(); 24 | return order; 25 | } 26 | 27 | public IAggregate Handle(StartShippingProcess command) 28 | { 29 | var order = _domainRepository.GetById(command.Id); 30 | order.StartShippingProcess(); 31 | return order; 32 | } 33 | 34 | public IAggregate Handle(CancelOrder command) 35 | { 36 | var order = _domainRepository.GetById(command.Id); 37 | order.Cancel(); 38 | return order; 39 | } 40 | 41 | public IAggregate Handle(ShipOrder command) 42 | { 43 | var order = _domainRepository.GetById(command.Id); 44 | order.ShipOrder(); 45 | return order; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain/CommandHandlers/ProductCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using CQRSShop.Contracts.Commands; 2 | using CQRSShop.Domain.Aggregates; 3 | using CQRSShop.Domain.Exceptions; 4 | using CQRSShop.Infrastructure; 5 | using CQRSShop.Infrastructure.Exceptions; 6 | 7 | namespace CQRSShop.Domain.CommandHandlers 8 | { 9 | internal class ProductCommandHandler : 10 | IHandle 11 | { 12 | private readonly IDomainRepository _domainRepository; 13 | 14 | public ProductCommandHandler(IDomainRepository domainRepository) 15 | { 16 | _domainRepository = domainRepository; 17 | } 18 | 19 | public IAggregate Handle(CreateProduct command) 20 | { 21 | try 22 | { 23 | var product = _domainRepository.GetById(command.Id); 24 | throw new ProductAlreadyExistsException(command.Id); 25 | } 26 | catch (AggregateNotFoundException) 27 | { 28 | // We expect not to find anything 29 | } 30 | return Product.Create(command.Id, command.Name, command.Price); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain/DomainEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CQRSShop.Contracts.Commands; 5 | using CQRSShop.Domain.CommandHandlers; 6 | using CQRSShop.Infrastructure; 7 | 8 | namespace CQRSShop.Domain 9 | { 10 | public class DomainEntry 11 | { 12 | private readonly CommandDispatcher _commandDispatcher; 13 | 14 | public DomainEntry(IDomainRepository domainRepository, IEnumerable> preExecutionPipe = null, IEnumerable> postExecutionPipe = null) 15 | { 16 | preExecutionPipe = preExecutionPipe ?? Enumerable.Empty>(); 17 | postExecutionPipe = CreatePostExecutionPipe(postExecutionPipe); 18 | _commandDispatcher = CreateCommandDispatcher(domainRepository, preExecutionPipe, postExecutionPipe); 19 | } 20 | 21 | public void ExecuteCommand(TCommand command) where TCommand : ICommand 22 | { 23 | _commandDispatcher.ExecuteCommand(command); 24 | } 25 | 26 | private CommandDispatcher CreateCommandDispatcher(IDomainRepository domainRepository, IEnumerable> preExecutionPipe, IEnumerable> postExecutionPipe) 27 | { 28 | var commandDispatcher = new CommandDispatcher(domainRepository, preExecutionPipe, postExecutionPipe); 29 | 30 | var customerCommandHandler = new CustomerCommandHandler(domainRepository); 31 | commandDispatcher.RegisterHandler(customerCommandHandler); 32 | commandDispatcher.RegisterHandler(customerCommandHandler); 33 | 34 | var productCommandHandler = new ProductCommandHandler(domainRepository); 35 | commandDispatcher.RegisterHandler(productCommandHandler); 36 | 37 | var basketCommandHandler = new BasketCommandHandler(domainRepository); 38 | commandDispatcher.RegisterHandler(basketCommandHandler); 39 | commandDispatcher.RegisterHandler(basketCommandHandler); 40 | commandDispatcher.RegisterHandler(basketCommandHandler); 41 | commandDispatcher.RegisterHandler(basketCommandHandler); 42 | commandDispatcher.RegisterHandler(basketCommandHandler); 43 | 44 | var orderCommandHanler = new OrderHandler(domainRepository); 45 | commandDispatcher.RegisterHandler(orderCommandHanler); 46 | commandDispatcher.RegisterHandler(orderCommandHanler); 47 | commandDispatcher.RegisterHandler(orderCommandHanler); 48 | commandDispatcher.RegisterHandler(orderCommandHanler); 49 | 50 | return commandDispatcher; 51 | } 52 | 53 | private IEnumerable> CreatePostExecutionPipe(IEnumerable> postExecutionPipe) 54 | { 55 | if (postExecutionPipe != null) 56 | { 57 | foreach (var action in postExecutionPipe) 58 | { 59 | yield return action; 60 | } 61 | } 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/CQRSShop.Domain/Exceptions/CustomerAlreadyExistsException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSShop.Domain.Exceptions 4 | { 5 | public class CustomerAlreadyExistsException : DuplicateAggregateException 6 | { 7 | public CustomerAlreadyExistsException(Guid id) : base(id) 8 | { 9 | 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain/Exceptions/DuplicateAggregateException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSShop.Domain.Exceptions 4 | { 5 | public abstract class DuplicateAggregateException : DomainException 6 | { 7 | protected DuplicateAggregateException(Guid id) : base(CreateMessage(id)) 8 | { 9 | 10 | } 11 | 12 | private static string CreateMessage(Guid id) 13 | { 14 | return string.Format("Aggregate already exists with id {0}", id); 15 | } 16 | } 17 | 18 | public class ProductAlreadyExistsException : DuplicateAggregateException 19 | { 20 | public ProductAlreadyExistsException(Guid id) : base(id) 21 | { 22 | } 23 | } 24 | 25 | public class MissingAddressException : DomainException 26 | { 27 | 28 | } 29 | 30 | public class UnexpectedPaymentException : DomainException 31 | { } 32 | 33 | public class OrderCancelledException : DomainException 34 | { } 35 | 36 | public class ShippingStartedException : DomainException {} 37 | 38 | public abstract class DomainException : Exception 39 | { 40 | public DomainException() 41 | { 42 | } 43 | 44 | protected DomainException(string createMessage) : base(createMessage) 45 | { 46 | } 47 | } 48 | 49 | public class InvalidOrderState : DomainException { } 50 | 51 | public class BasketAlreadExistsException : DuplicateAggregateException 52 | { 53 | public BasketAlreadExistsException(Guid id) : base(id) 54 | { 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/CQRSShop.Domain/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CQRSShop.Domain")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CQRSShop.Domain")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("6f266ab2-d0e6-4f09-bbf8-f69bdf108bbd")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/CQRSShop.Infrastructure/AggregateBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace CQRSShop.Infrastructure 5 | { 6 | public class AggregateBase : IAggregate 7 | { 8 | public int Version 9 | { 10 | get 11 | { 12 | return _version; 13 | } 14 | protected set 15 | { 16 | _version = value; 17 | } 18 | } 19 | 20 | public Guid Id { get; protected set; } 21 | 22 | private List _uncommitedEvents = new List(); 23 | private Dictionary> _routes = new Dictionary>(); 24 | private int _version = -1; 25 | 26 | public void RaiseEvent(IEvent @event) 27 | { 28 | ApplyEvent(@event); 29 | _uncommitedEvents.Add(@event); 30 | } 31 | 32 | protected void RegisterTransition(Action transition) where T : class 33 | { 34 | _routes.Add(typeof(T), o => transition(o as T)); 35 | } 36 | 37 | public void ApplyEvent(IEvent @event) 38 | { 39 | var eventType = @event.GetType(); 40 | if (_routes.ContainsKey(eventType)) 41 | { 42 | _routes[eventType](@event); 43 | } 44 | Version++; 45 | } 46 | 47 | public IEnumerable UncommitedEvents() 48 | { 49 | return _uncommitedEvents; 50 | } 51 | 52 | public void ClearUncommitedEvents() 53 | { 54 | _uncommitedEvents.Clear(); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /src/CQRSShop.Infrastructure/CQRSShop.Infrastructure.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {57AE018A-BA49-471D-97D7-C4AD2040D4B0} 8 | Library 9 | Properties 10 | CQRSShop.Infrastructure 11 | CQRSShop.Infrastructure 12 | v4.5.1 13 | 512 14 | ..\ 15 | true 16 | 17 | 18 | true 19 | full 20 | false 21 | bin\Debug\ 22 | DEBUG;TRACE 23 | prompt 24 | 4 25 | 26 | 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\packages\EventStore.Client.3.0.0-rc2\lib\net40\EventStore.ClientAPI.dll 37 | 38 | 39 | False 40 | ..\packages\Newtonsoft.Json.6.0.3\lib\net45\Newtonsoft.Json.dll 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 73 | 74 | 75 | 76 | 83 | -------------------------------------------------------------------------------- /src/CQRSShop.Infrastructure/CommandDispatcher.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | namespace CQRSShop.Infrastructure 6 | { 7 | public class CommandDispatcher 8 | { 9 | private Dictionary> _routes; 10 | private IDomainRepository _domainRepository; 11 | private readonly IEnumerable> _postExecutionPipe; 12 | private readonly IEnumerable> _preExecutionPipe; 13 | 14 | public CommandDispatcher(IDomainRepository domainRepository, IEnumerable> preExecutionPipe, IEnumerable> postExecutionPipe) 15 | { 16 | _domainRepository = domainRepository; 17 | _postExecutionPipe = postExecutionPipe; 18 | _preExecutionPipe = preExecutionPipe ?? Enumerable.Empty>(); 19 | _routes = new Dictionary>(); 20 | } 21 | 22 | public void RegisterHandler(IHandle handler) where TCommand : class, ICommand 23 | { 24 | _routes.Add(typeof (TCommand), command => handler.Handle(command as TCommand)); 25 | } 26 | 27 | public void ExecuteCommand(TCommand command) where TCommand : ICommand 28 | { 29 | var commandType = command.GetType(); 30 | 31 | RunPreExecutionPipe(command); 32 | if (!_routes.ContainsKey(commandType)) 33 | { 34 | throw new ApplicationException("Missing handler for " + commandType.Name); 35 | } 36 | var aggregate = _routes[commandType](command); 37 | var savedEvents = _domainRepository.Save(aggregate); 38 | RunPostExecutionPipe(savedEvents); 39 | } 40 | 41 | private void RunPostExecutionPipe(IEnumerable savedEvents) 42 | { 43 | foreach (var savedEvent in savedEvents) 44 | { 45 | foreach (var action in _postExecutionPipe) 46 | { 47 | action(savedEvent); 48 | } 49 | } 50 | } 51 | 52 | private void RunPreExecutionPipe(ICommand command) 53 | { 54 | foreach (var action in _preExecutionPipe) 55 | { 56 | action(command); 57 | } 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /src/CQRSShop.Infrastructure/DomainRepositoryBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace CQRSShop.Infrastructure 5 | { 6 | public abstract class DomainRepositoryBase : IDomainRepository 7 | { 8 | public abstract IEnumerable Save(TAggregate aggregate) where TAggregate : IAggregate; 9 | public abstract TResult GetById(Guid id) where TResult : IAggregate, new(); 10 | 11 | protected int CalculateExpectedVersion(IAggregate aggregate, List events) 12 | { 13 | var expectedVersion = aggregate.Version - events.Count; 14 | return expectedVersion; 15 | } 16 | 17 | protected TResult BuildAggregate(IEnumerable events) where TResult : IAggregate, new() 18 | { 19 | var result = new TResult(); 20 | foreach (var @event in events) 21 | { 22 | result.ApplyEvent(@event); 23 | } 24 | return result; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/CQRSShop.Infrastructure/EventStoreDomainRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using CQRSShop.Infrastructure.Exceptions; 6 | using EventStore.ClientAPI; 7 | using Newtonsoft.Json; 8 | 9 | namespace CQRSShop.Infrastructure 10 | { 11 | public class EventStoreDomainRepository : DomainRepositoryBase 12 | { 13 | private IEventStoreConnection _connection; 14 | private const string Category = "cqrsshop"; 15 | 16 | public EventStoreDomainRepository(IEventStoreConnection connection) 17 | { 18 | _connection = connection; 19 | } 20 | 21 | private string AggregateToStreamName(Type type, Guid id) 22 | { 23 | return string.Format("{0}-{1}-{2}", Category, type.Name, id); 24 | } 25 | 26 | public override IEnumerable Save(TAggregate aggregate) 27 | { 28 | var events = aggregate.UncommitedEvents().ToList(); 29 | var expectedVersion = CalculateExpectedVersion(aggregate, events); 30 | var eventData = events.Select(CreateEventData); 31 | var streamName = AggregateToStreamName(aggregate.GetType(), aggregate.Id); 32 | _connection.AppendToStream(streamName, expectedVersion, eventData); 33 | return events; 34 | } 35 | 36 | public override TResult GetById(Guid id) 37 | { 38 | var streamName = AggregateToStreamName(typeof(TResult), id); 39 | var eventsSlice = _connection.ReadStreamEventsForward(streamName, 0, int.MaxValue, false); 40 | if (eventsSlice.Status == SliceReadStatus.StreamNotFound) 41 | { 42 | throw new AggregateNotFoundException("Could not found aggregate of type " + typeof(TResult) + " and id " + id); 43 | } 44 | var deserializedEvents = eventsSlice.Events.Select(e => 45 | { 46 | var metadata = DeserializeObject>(e.OriginalEvent.Metadata); 47 | var eventData = DeserializeObject(e.OriginalEvent.Data, metadata[EventClrTypeHeader]); 48 | return eventData as IEvent; 49 | }); 50 | return BuildAggregate(deserializedEvents); 51 | } 52 | 53 | private T DeserializeObject(byte[] data) 54 | { 55 | return (T)(DeserializeObject(data, typeof(T).AssemblyQualifiedName)); 56 | } 57 | 58 | private object DeserializeObject(byte[] data, string typeName) 59 | { 60 | var jsonString = Encoding.UTF8.GetString(data); 61 | return JsonConvert.DeserializeObject(jsonString, Type.GetType(typeName)); 62 | } 63 | 64 | public EventData CreateEventData(object @event) 65 | { 66 | var eventHeaders = new Dictionary() 67 | { 68 | { 69 | EventClrTypeHeader, @event.GetType().AssemblyQualifiedName 70 | }, 71 | { 72 | "Domain", "Enheter" 73 | } 74 | }; 75 | var eventDataHeaders = SerializeObject(eventHeaders); 76 | var data = SerializeObject(@event); 77 | var eventData = new EventData(Guid.NewGuid(), @event.GetType().Name, true, data, eventDataHeaders); 78 | return eventData; 79 | } 80 | 81 | private byte[] SerializeObject(object obj) 82 | { 83 | var jsonObj = JsonConvert.SerializeObject(obj); 84 | var data = Encoding.UTF8.GetBytes(jsonObj); 85 | return data; 86 | } 87 | 88 | public string EventClrTypeHeader = "EventClrTypeName"; 89 | } 90 | } -------------------------------------------------------------------------------- /src/CQRSShop.Infrastructure/Exceptions/AggregateNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSShop.Infrastructure.Exceptions 4 | { 5 | public class AggregateNotFoundException : Exception 6 | { 7 | public AggregateNotFoundException(string message) 8 | : base(message) 9 | { 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/CQRSShop.Infrastructure/IAggregate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace CQRSShop.Infrastructure 5 | { 6 | public interface IAggregate 7 | { 8 | IEnumerable UncommitedEvents(); 9 | void ClearUncommitedEvents(); 10 | int Version { get; } 11 | Guid Id { get; } 12 | void ApplyEvent(IEvent @event); 13 | } 14 | } -------------------------------------------------------------------------------- /src/CQRSShop.Infrastructure/ICommand.cs: -------------------------------------------------------------------------------- 1 | namespace CQRSShop.Infrastructure 2 | { 3 | public interface ICommand 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /src/CQRSShop.Infrastructure/IDomainRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace CQRSShop.Infrastructure 5 | { 6 | public interface IDomainRepository 7 | { 8 | IEnumerable Save(TAggregate aggregate) where TAggregate : IAggregate; 9 | TResult GetById(Guid id) where TResult : IAggregate, new(); 10 | } 11 | } -------------------------------------------------------------------------------- /src/CQRSShop.Infrastructure/IEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSShop.Infrastructure 4 | { 5 | public interface IEvent 6 | { 7 | Guid Id { get; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/CQRSShop.Infrastructure/IHandle.cs: -------------------------------------------------------------------------------- 1 | namespace CQRSShop.Infrastructure 2 | { 3 | public interface IHandle where TCommand : ICommand 4 | { 5 | IAggregate Handle(TCommand command); 6 | } 7 | } -------------------------------------------------------------------------------- /src/CQRSShop.Infrastructure/IdGenerator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CQRSShop.Infrastructure 4 | { 5 | public class IdGenerator 6 | { 7 | private static Func _generator; 8 | 9 | public static Func GenerateGuid 10 | { 11 | get 12 | { 13 | _generator = _generator ?? Guid.NewGuid; 14 | return _generator; 15 | } 16 | set { _generator = value; } 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /src/CQRSShop.Infrastructure/InMemoryDomainRespository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CQRSShop.Infrastructure.Exceptions; 5 | using EventStore.ClientAPI.Exceptions; 6 | using Newtonsoft.Json; 7 | 8 | namespace CQRSShop.Infrastructure 9 | { 10 | public class InMemoryDomainRespository : DomainRepositoryBase 11 | { 12 | public Dictionary> _eventStore = new Dictionary>(); 13 | private List _latestEvents = new List(); 14 | private JsonSerializerSettings _serializationSettings; 15 | 16 | public InMemoryDomainRespository() 17 | { 18 | _serializationSettings = new JsonSerializerSettings 19 | { 20 | TypeNameHandling = TypeNameHandling.All 21 | }; 22 | } 23 | 24 | public override IEnumerable Save(TAggregate aggregate) 25 | { 26 | var eventsToSave = aggregate.UncommitedEvents().ToList(); 27 | var serializedEvents = eventsToSave.Select(Serialize).ToList(); 28 | var expectedVersion = CalculateExpectedVersion(aggregate, eventsToSave); 29 | if (expectedVersion < 0) 30 | { 31 | _eventStore.Add(aggregate.Id, serializedEvents); 32 | } 33 | else 34 | { 35 | var existingEvents = _eventStore[aggregate.Id]; 36 | var currentversion = existingEvents.Count - 1; 37 | if (currentversion != expectedVersion) 38 | { 39 | throw new WrongExpectedVersionException("Expected version " + expectedVersion + 40 | " but the version is " + currentversion); 41 | } 42 | existingEvents.AddRange(serializedEvents); 43 | } 44 | _latestEvents.AddRange(eventsToSave); 45 | aggregate.ClearUncommitedEvents(); 46 | return eventsToSave; 47 | } 48 | 49 | private string Serialize(IEvent arg) 50 | { 51 | return JsonConvert.SerializeObject(arg, _serializationSettings); 52 | } 53 | 54 | public IEnumerable GetLatestEvents() 55 | { 56 | return _latestEvents; 57 | } 58 | 59 | public override TResult GetById(Guid id) 60 | { 61 | if (_eventStore.ContainsKey(id)) 62 | { 63 | var events = _eventStore[id]; 64 | var deserializedEvents = events.Select(e => JsonConvert.DeserializeObject(e, _serializationSettings) as IEvent); 65 | return BuildAggregate(deserializedEvents); 66 | } 67 | throw new AggregateNotFoundException("Could not found aggregate of type " + typeof(TResult) + " and id " + id); 68 | } 69 | 70 | public void AddEvents(Dictionary> eventsForAggregates) 71 | { 72 | foreach (var eventsForAggregate in eventsForAggregates) 73 | { 74 | _eventStore.Add(eventsForAggregate.Key, eventsForAggregate.Value.Select(Serialize).ToList()); 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /src/CQRSShop.Infrastructure/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CQRSShop.Infrastructure")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CQRSShop.Infrastructure")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("e64ab8c5-32f3-473b-b3d6-fb02902932ac")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/CQRSShop.Infrastructure/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/CQRSShop.Search/CQRSShop.Search.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {EA9F7365-0A26-44FD-86D1-A645B5671FEA} 8 | Library 9 | Properties 10 | CQRSShop.Search 11 | CQRSShop.Search 12 | v4.5.1 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 53 | -------------------------------------------------------------------------------- /src/CQRSShop.Search/Class1.cs: -------------------------------------------------------------------------------- 1 | namespace CQRSShop.Search 2 | { 3 | public class Class1 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/CQRSShop.Search/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CQRSShop.Search")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CQRSShop.Search")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("83a2dc37-f293-472e-aeb4-a0655f3106b6")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/CQRSShop.Service/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/CQRSShop.Service/CQRSShop.Service.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {A78A0026-6CE5-4864-914D-07F4CDE4A4DB} 8 | Exe 9 | Properties 10 | CQRSShop.Service 11 | CQRSShop.Service 12 | v4.5.1 13 | 512 14 | true 15 | ..\ 16 | true 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | AnyCPU 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | ..\CQRSShop.Contracts\bin\Debug\CQRSShop.Contracts.dll 40 | 41 | 42 | ..\packages\Elasticsearch.Net.1.0.0-beta1\lib\Elasticsearch.Net.dll 43 | 44 | 45 | ..\packages\EventStore.Client.3.0.0-rc2\lib\net40\EventStore.ClientAPI.dll 46 | 47 | 48 | ..\packages\Neo4jClient.1.0.0.656\lib\net40\Neo4jClient.dll 49 | 50 | 51 | ..\packages\NEST.1.0.0-beta1\lib\Nest.dll 52 | True 53 | 54 | 55 | False 56 | ..\packages\Newtonsoft.Json.6.0.3\lib\net45\Newtonsoft.Json.dll 57 | 58 | 59 | 60 | 61 | 62 | ..\packages\Microsoft.Net.Http.2.2.22\lib\net45\System.Net.Http.Extensions.dll 63 | 64 | 65 | ..\packages\Microsoft.Net.Http.2.2.22\lib\net45\System.Net.Http.Primitives.dll 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | ..\packages\Topshelf.3.1.3\lib\net40-full\Topshelf.dll 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | {57AE018A-BA49-471D-97D7-C4AD2040D4B0} 95 | CQRSShop.Infrastructure 96 | 97 | 98 | 99 | 100 | 101 | 102 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 118 | -------------------------------------------------------------------------------- /src/CQRSShop.Service/Documents/Basket.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CQRSShop.Contracts.Types; 3 | using Nest; 4 | 5 | namespace CQRSShop.Service.Documents 6 | { 7 | public class Basket 8 | { 9 | [ElasticProperty(Index = FieldIndexOption.not_analyzed)] 10 | public Guid Id { get; set; } 11 | [ElasticProperty(Type = FieldType.nested)] 12 | public OrderLine[] OrderLines { get; set; } 13 | public BasketState BasketState { get; set; } 14 | [ElasticProperty(Index = FieldIndexOption.not_analyzed)] 15 | public Guid OrderId { get; set; } 16 | } 17 | 18 | public enum BasketState 19 | { 20 | Shopping, 21 | CheckingOut, 22 | CheckedOut, 23 | Paid 24 | } 25 | } -------------------------------------------------------------------------------- /src/CQRSShop.Service/Documents/Customer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Nest; 3 | 4 | namespace CQRSShop.Service.Documents 5 | { 6 | public class Customer 7 | { 8 | [ElasticProperty(Index = FieldIndexOption.not_analyzed)] 9 | public Guid Id { get; set; } 10 | public string Name { get; set; } 11 | public bool IsPreferred { get; set; } 12 | public int Discount { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /src/CQRSShop.Service/Documents/Product.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Nest; 3 | 4 | namespace CQRSShop.Service.Documents 5 | { 6 | public class Product 7 | { 8 | [ElasticProperty(Index = FieldIndexOption.not_analyzed)] 9 | public Guid Id { get; set; } 10 | public string Name { get; set; } 11 | public int Price { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /src/CQRSShop.Service/EventSerialization.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using EventStore.ClientAPI; 5 | using Newtonsoft.Json; 6 | 7 | namespace CQRSShop.Service 8 | { 9 | public class EventSerialization 10 | { 11 | public static object DeserializeEvent(RecordedEvent originalEvent) 12 | { 13 | if (originalEvent.Metadata != null) 14 | { 15 | var metadata = DeserializeObject>(originalEvent.Metadata); 16 | if (metadata != null && metadata.ContainsKey(EventClrTypeHeader)) 17 | { 18 | var eventData = DeserializeObject(originalEvent.Data, metadata[EventClrTypeHeader]); 19 | return eventData; 20 | } 21 | } 22 | return null; 23 | } 24 | 25 | private static T DeserializeObject(byte[] data) 26 | { 27 | return (T)(DeserializeObject(data, typeof(T).AssemblyQualifiedName)); 28 | } 29 | 30 | private static object DeserializeObject(byte[] data, string typeName) 31 | { 32 | var jsonString = Encoding.UTF8.GetString(data); 33 | try 34 | { 35 | return JsonConvert.DeserializeObject(jsonString, Type.GetType(typeName)); 36 | } 37 | catch (JsonReaderException) 38 | { 39 | return null; 40 | } 41 | } 42 | public static string EventClrTypeHeader = "EventClrTypeName"; 43 | } 44 | } -------------------------------------------------------------------------------- /src/CQRSShop.Service/EventStoreConnectionWrapper.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using EventStore.ClientAPI; 3 | using EventStore.ClientAPI.SystemData; 4 | 5 | namespace CQRSShop.Service 6 | { 7 | public class EventStoreConnectionWrapper 8 | { 9 | private static IEventStoreConnection _connection; 10 | 11 | public static IEventStoreConnection Connect() 12 | { 13 | ConnectionSettings settings = 14 | ConnectionSettings.Create() 15 | .UseConsoleLogger() 16 | .KeepReconnecting() 17 | .SetDefaultUserCredentials(new UserCredentials("admin", "changeit")); 18 | var endPoint = new IPEndPoint(IPAddress.Loopback, 1113); 19 | _connection = EventStoreConnection.Create(settings, endPoint, null); 20 | _connection.Connect(); 21 | return _connection; 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /src/CQRSShop.Service/Indexer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CQRSShop.Service.Documents; 3 | using Nest; 4 | 5 | namespace CQRSShop.Service 6 | { 7 | internal class Indexer 8 | { 9 | private readonly ElasticClient _esClient; 10 | private string _index = "cqrsshop"; 11 | 12 | public Indexer() 13 | { 14 | var settings = new ConnectionSettings(new Uri("http://localhost:9200")); 15 | settings.SetDefaultIndex(_index); 16 | _esClient = new ElasticClient(settings); 17 | } 18 | 19 | public TDocument Get(Guid id) where TDocument : class 20 | { 21 | return _esClient.Get(id.ToString()).Source; 22 | } 23 | 24 | public void Index(TDocument document) where TDocument : class 25 | { 26 | _esClient.Index(document, y => y.Index(_index)); 27 | } 28 | 29 | public void Init() 30 | { 31 | _esClient.CreateIndex(_index, y => y 32 | .AddMapping(m => m.MapFromAttributes()) 33 | .AddMapping(m => m.MapFromAttributes()) 34 | .AddMapping(m => m.MapFromAttributes())); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /src/CQRSShop.Service/IndexingServie.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using CQRSShop.Contracts.Events; 5 | using CQRSShop.Service.Documents; 6 | using EventStore.ClientAPI; 7 | using Neo4jClient; 8 | 9 | namespace CQRSShop.Service 10 | { 11 | internal class IndexingServie 12 | { 13 | private Indexer _indexer; 14 | private Dictionary> _eventHandlerMapping; 15 | private Position? _latestPosition; 16 | private IEventStoreConnection _connection; 17 | private GraphClient _graphClient; 18 | 19 | public void Start() 20 | { 21 | _graphClient = CreateGraphClient(); 22 | _indexer = CreateIndexer(); 23 | _eventHandlerMapping = CreateEventHandlerMapping(); 24 | ConnectToEventstore(); 25 | } 26 | 27 | private GraphClient CreateGraphClient() 28 | { 29 | var graphClient = new GraphClient(new Uri("http://localhost:7474/db/data")); 30 | graphClient.Connect(); 31 | DeleteAll(graphClient); 32 | return graphClient; 33 | } 34 | 35 | private void DeleteAll(GraphClient graphClient) 36 | { 37 | graphClient.Cypher.Match("(n)") 38 | .OptionalMatch("(n)-[r]-()") 39 | .Delete("n,r") 40 | .ExecuteWithoutResults(); 41 | } 42 | 43 | private Indexer CreateIndexer() 44 | { 45 | var indexer = new Indexer(); 46 | indexer.Init(); 47 | return indexer; 48 | } 49 | 50 | private void ConnectToEventstore() 51 | { 52 | 53 | _latestPosition = Position.Start; 54 | _connection = EventStoreConnectionWrapper.Connect(); 55 | _connection.Connected += 56 | (sender, args) => _connection.SubscribeToAllFrom(_latestPosition, false, HandleEvent); 57 | Console.WriteLine("Indexing service started"); 58 | } 59 | 60 | private void HandleEvent(EventStoreCatchUpSubscription arg1, ResolvedEvent arg2) 61 | { 62 | var @event = EventSerialization.DeserializeEvent(arg2.OriginalEvent); 63 | if (@event != null) 64 | { 65 | var eventType = @event.GetType(); 66 | if (_eventHandlerMapping.ContainsKey(eventType)) 67 | { 68 | _eventHandlerMapping[eventType](@event); 69 | } 70 | } 71 | _latestPosition = arg2.OriginalPosition; 72 | } 73 | 74 | private Dictionary> CreateEventHandlerMapping() 75 | { 76 | return new Dictionary>() 77 | { 78 | {typeof (CustomerCreated), o => Handle(o as CustomerCreated)}, 79 | {typeof (CustomerMarkedAsPreferred), o => Handle(o as CustomerMarkedAsPreferred)}, 80 | {typeof (BasketCreated), o => Handle(o as BasketCreated)}, 81 | {typeof (ItemAdded), o => Handle(o as ItemAdded)}, 82 | {typeof (CustomerIsCheckingOutBasket), o => Handle(o as CustomerIsCheckingOutBasket)}, 83 | {typeof (BasketCheckedOut), o => Handle(o as BasketCheckedOut)}, 84 | {typeof (OrderCreated), o => Handle(o as OrderCreated)}, 85 | {typeof (ProductCreated), o => Handle(o as ProductCreated)} 86 | }; 87 | } 88 | 89 | private void Handle(OrderCreated evt) 90 | { 91 | var existinBasket = _indexer.Get(evt.BasketId); 92 | existinBasket.BasketState = BasketState.Paid; 93 | _indexer.Index(existinBasket); 94 | 95 | _graphClient.Cypher 96 | .Match("(customer:Customer)-[:HAS_BASKET]->(basket:Basket)-[]->(product:Product)") 97 | .Where((Basket basket) => basket.Id == evt.BasketId) 98 | .Create("customer-[:BOUGHT]->product") 99 | .ExecuteWithoutResults(); 100 | } 101 | 102 | private void Handle(BasketCheckedOut evt) 103 | { 104 | var basket = _indexer.Get(evt.Id); 105 | basket.BasketState = BasketState.CheckedOut; 106 | _indexer.Index(basket); 107 | } 108 | 109 | private void Handle(CustomerIsCheckingOutBasket evt) 110 | { 111 | var basket = _indexer.Get(evt.Id); 112 | basket.BasketState = BasketState.CheckingOut; 113 | _indexer.Index(basket); 114 | } 115 | 116 | private void Handle(ItemAdded evt) 117 | { 118 | var existingBasket = _indexer.Get(evt.Id); 119 | var orderLines = existingBasket.OrderLines; 120 | if (orderLines == null || orderLines.Length == 0) 121 | { 122 | existingBasket.OrderLines = new[] {evt.OrderLine}; 123 | } 124 | else 125 | { 126 | var orderLineList = orderLines.ToList(); 127 | orderLineList.Add(evt.OrderLine); 128 | existingBasket.OrderLines = orderLineList.ToArray(); 129 | } 130 | 131 | _indexer.Index(existingBasket); 132 | 133 | _graphClient.Cypher 134 | .Match("(basket:Basket)", "(product:Product)") 135 | .Where((Basket basket) => basket.Id == evt.Id) 136 | .AndWhere((Product product) => product.Id == evt.OrderLine.ProductId) 137 | .Create("basket-[:HAS_ORDERLINE {orderLine}]->product") 138 | .WithParam("orderLine", evt.OrderLine) 139 | .ExecuteWithoutResults(); 140 | } 141 | 142 | private void Handle(BasketCreated evt) 143 | { 144 | var newBasket = new Basket() 145 | { 146 | Id = evt.Id, 147 | OrderLines = null, 148 | BasketState = BasketState.Shopping 149 | }; 150 | _indexer.Index(newBasket); 151 | _graphClient.Cypher 152 | .Create("(basket:Basket {newBasket})") 153 | .WithParam("newBasket", newBasket) 154 | .ExecuteWithoutResults(); 155 | 156 | _graphClient.Cypher 157 | .Match("(customer:Customer)", "(basket:Basket)") 158 | .Where((Customer customer) => customer.Id == evt.CustomerId) 159 | .AndWhere((Basket basket) => basket.Id == evt.Id) 160 | .Create("customer-[:HAS_BASKET]->basket") 161 | .ExecuteWithoutResults(); 162 | } 163 | 164 | private void Handle(ProductCreated evt) 165 | { 166 | var product = new Product() 167 | { 168 | Id = evt.Id, 169 | Name = evt.Name, 170 | Price = evt.Price 171 | }; 172 | _indexer.Index(product); 173 | _graphClient.Cypher 174 | .Create("(product:Product {newProduct})") 175 | .WithParam("newProduct", product) 176 | .ExecuteWithoutResults(); 177 | } 178 | 179 | private void Handle(CustomerMarkedAsPreferred evt) 180 | { 181 | var customer = _indexer.Get(evt.Id); 182 | customer.IsPreferred = true; 183 | customer.Discount = evt.Discount; 184 | _indexer.Index(customer); 185 | 186 | _graphClient.Cypher 187 | .Match("(c:Customer)") 188 | .Where((Customer c) => c.Id == customer.Id) 189 | .Set("c = {c}") 190 | .WithParam("c", customer) 191 | .ExecuteWithoutResults(); 192 | } 193 | 194 | private void Handle(CustomerCreated evt) 195 | { 196 | var customer = new Customer() 197 | { 198 | Id = evt.Id, 199 | Name = evt.Name 200 | }; 201 | _indexer.Index(customer); 202 | 203 | _graphClient.Cypher 204 | .Create("(customer:Customer {newCustomer})") 205 | .WithParam("newCustomer", customer) 206 | .ExecuteWithoutResults(); 207 | } 208 | 209 | public void Stop() 210 | { 211 | } 212 | } 213 | } -------------------------------------------------------------------------------- /src/CQRSShop.Service/Program.cs: -------------------------------------------------------------------------------- 1 | using Topshelf; 2 | 3 | namespace CQRSShop.Service 4 | { 5 | class Program 6 | { 7 | public static void Main(string[] args) 8 | { 9 | HostFactory.Run(x => 10 | { 11 | x.Service(s => 12 | { 13 | s.ConstructUsing(name => new IndexingServie()); 14 | s.WhenStarted(tc => tc.Start()); 15 | s.WhenStopped(tc => tc.Stop()); 16 | }); 17 | x.RunAsLocalSystem(); 18 | 19 | x.SetDescription("CQRSShop.Service"); 20 | x.SetDisplayName("CQRSShop.Service"); 21 | x.SetServiceName("CQRSShop.Service"); 22 | }); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/CQRSShop.Service/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CQRSShope.Service")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CQRSShope.Service")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("6b3b0c07-38b6-4519-86a2-ff31d0206749")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/CQRSShop.Service/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/CQRSShop.Web/Api/BasePostEndpoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CQRSShop.Domain; 3 | using CQRSShop.Infrastructure; 4 | using Simple.Web; 5 | using Simple.Web.Behaviors; 6 | 7 | namespace CQRSShop.Web.Api 8 | { 9 | public abstract class BasePostEndpoint : IPost, IInput where TCommand : ICommand 10 | { 11 | public Status Post() 12 | { 13 | try 14 | { 15 | var connection = Configuration.CreateConnection(); 16 | var domainRepository = new EventStoreDomainRepository(connection); 17 | var application = new DomainEntry(domainRepository); 18 | application.ExecuteCommand(Input); 19 | } 20 | catch (Exception) 21 | { 22 | return Status.InternalServerError; 23 | } 24 | 25 | return Status.OK; 26 | } 27 | 28 | public TCommand Input { set; private get; } 29 | } 30 | } -------------------------------------------------------------------------------- /src/CQRSShop.Web/Api/Basket/Checkout/PostEndpoint.cs: -------------------------------------------------------------------------------- 1 | using CQRSShop.Contracts.Commands; 2 | using Simple.Web; 3 | 4 | namespace CQRSShop.Web.Api.Basket.Checkout 5 | { 6 | [UriTemplate("/api/basket/{BasketId}/checkout")] 7 | public class PostEndpoint : BasePostEndpoint 8 | { 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /src/CQRSShop.Web/Api/Basket/Items/PostEndpoint.cs: -------------------------------------------------------------------------------- 1 | using CQRSShop.Contracts.Commands; 2 | using Simple.Web; 3 | 4 | namespace CQRSShop.Web.Api.Basket.Items 5 | { 6 | [UriTemplate("/api/basket/{BasketId}/items")] 7 | public class PostEndpoint : BasePostEndpoint 8 | { 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /src/CQRSShop.Web/Api/Basket/Pay/PostEndpoint.cs: -------------------------------------------------------------------------------- 1 | using CQRSShop.Contracts.Commands; 2 | using Simple.Web; 3 | 4 | namespace CQRSShop.Web.Api.Basket.Pay 5 | { 6 | [UriTemplate("/api/basket/{BasketId}/pay")] 7 | public class PostEndpoint : BasePostEndpoint 8 | { 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /src/CQRSShop.Web/Api/Basket/PostEndpoint.cs: -------------------------------------------------------------------------------- 1 | using CQRSShop.Contracts.Commands; 2 | using Simple.Web; 3 | using Simple.Web.Links; 4 | 5 | namespace CQRSShop.Web.Api.Basket 6 | { 7 | [UriTemplate("/api/basket")] 8 | [Root(Rel = "basket", Title = "Basket", Type = "application/vnd.cqrsshop.createbasket")] 9 | public class PostEndpoint : BasePostEndpoint 10 | { 11 | 12 | } 13 | } -------------------------------------------------------------------------------- /src/CQRSShop.Web/Api/Basket/Proceed/PostEndpoint.cs: -------------------------------------------------------------------------------- 1 | using CQRSShop.Contracts.Commands; 2 | using Simple.Web; 3 | 4 | namespace CQRSShop.Web.Api.Basket.Proceed 5 | { 6 | [UriTemplate("/api/basket/{BasketId}/proceed")] 7 | public class PostEndpoint : BasePostEndpoint 8 | { 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /src/CQRSShop.Web/Api/Customer/PostEndpoint.cs: -------------------------------------------------------------------------------- 1 | using CQRSShop.Contracts.Commands; 2 | using Simple.Web; 3 | using Simple.Web.Links; 4 | 5 | namespace CQRSShop.Web.Api.Customer 6 | { 7 | [UriTemplate("/api/customer")] 8 | [Root(Rel = "order", Title = "Order", Type = "application/vnd.cqrsshop.createcustomer")] 9 | public class PostEndpoint : BasePostEndpoint 10 | { 11 | 12 | } 13 | } -------------------------------------------------------------------------------- /src/CQRSShop.Web/Api/Customer/Preferred/PostEndpoint.cs: -------------------------------------------------------------------------------- 1 | using CQRSShop.Contracts.Commands; 2 | using Simple.Web; 3 | 4 | namespace CQRSShop.Web.Api.Customer.Preferred 5 | { 6 | [UriTemplate("/api/customer/{CustomerId}/preferred")] 7 | public class PostEndpoint : BasePostEndpoint 8 | { 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /src/CQRSShop.Web/Api/GetEndpoint.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Simple.Web; 3 | using Simple.Web.Behaviors; 4 | using Simple.Web.Links; 5 | 6 | namespace CQRSShop.Web.Api 7 | { 8 | [UriTemplate("/api")] 9 | public class GetEndpoint : IGet, IOutput> 10 | { 11 | public Status Get() 12 | { 13 | Output = LinkHelper.GetRootLinks(); 14 | return 200; 15 | } 16 | 17 | public IEnumerable Output { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /src/CQRSShop.Web/Api/Order/Approve/PostEndpoint.cs: -------------------------------------------------------------------------------- 1 | using CQRSShop.Contracts.Commands; 2 | using Simple.Web; 3 | 4 | namespace CQRSShop.Web.Api.Order.Approve 5 | { 6 | [UriTemplate("/api/order/{OrderId}/approve")] 7 | public class PostEndpoint : BasePostEndpoint 8 | { 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /src/CQRSShop.Web/Api/Order/Cancel/PostEndpoint.cs: -------------------------------------------------------------------------------- 1 | using CQRSShop.Contracts.Commands; 2 | using Simple.Web; 3 | 4 | namespace CQRSShop.Web.Api.Order.Cancel 5 | { 6 | [UriTemplate("/api/order/{OrderId}/cancel")] 7 | public class PostEndpoint : BasePostEndpoint 8 | { 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /src/CQRSShop.Web/Api/Order/Ship/PostEndpoint.cs: -------------------------------------------------------------------------------- 1 | using CQRSShop.Contracts.Commands; 2 | using Simple.Web; 3 | 4 | namespace CQRSShop.Web.Api.Order.Ship 5 | { 6 | [UriTemplate("/api/order/{OrderId}/ship")] 7 | public class PostEndpoint : BasePostEndpoint 8 | { 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /src/CQRSShop.Web/Api/Order/StartShipping/PostEndpoint.cs: -------------------------------------------------------------------------------- 1 | using CQRSShop.Contracts.Commands; 2 | using Simple.Web; 3 | 4 | namespace CQRSShop.Web.Api.Order.StartShipping 5 | { 6 | [UriTemplate("/api/order/{OrderId}/startshipping")] 7 | public class PostEndpoint : BasePostEndpoint 8 | { 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /src/CQRSShop.Web/Api/Product/PostEndpoint.cs: -------------------------------------------------------------------------------- 1 | using CQRSShop.Contracts.Commands; 2 | using Simple.Web; 3 | using Simple.Web.Links; 4 | 5 | namespace CQRSShop.Web.Api.Product 6 | { 7 | [UriTemplate("/api/product")] 8 | [Root(Rel = "product", Title = "Product", Type = "application/vnd.cqrsshop.createproduct")] 9 | public class PostEndpoint : BasePostEndpoint 10 | { 11 | 12 | } 13 | } -------------------------------------------------------------------------------- /src/CQRSShop.Web/CQRSShop.Web.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | 8 | 9 | 2.0 10 | {4E7B0EF2-E6C4-4795-8665-32D604CC8C82} 11 | {349c5851-65df-11da-9384-00065b846f21};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} 12 | Library 13 | Properties 14 | CQRSShop.Web 15 | CQRSShop.Web 16 | v4.5.1 17 | true 18 | 19 | 20 | 21 | 22 | ..\ 23 | true 24 | 25 | 26 | true 27 | full 28 | false 29 | bin\ 30 | DEBUG;TRACE 31 | prompt 32 | 4 33 | 34 | 35 | pdbonly 36 | true 37 | bin\ 38 | TRACE 39 | prompt 40 | 4 41 | 42 | 43 | 44 | False 45 | ..\packages\EventStore.Client.3.0.0-rc2\lib\net40\EventStore.ClientAPI.dll 46 | 47 | 48 | 49 | ..\packages\Microsoft.Owin.2.1.0\lib\net45\Microsoft.Owin.dll 50 | 51 | 52 | ..\packages\Microsoft.Owin.Host.SystemWeb.2.1.0\lib\net45\Microsoft.Owin.Host.SystemWeb.dll 53 | True 54 | 55 | 56 | False 57 | ..\packages\Newtonsoft.Json.6.0.3\lib\net45\Newtonsoft.Json.dll 58 | 59 | 60 | ..\packages\Owin.1.0\lib\net40\Owin.dll 61 | 62 | 63 | ..\packages\Simple.Web.0.12.1-pre\lib\net40\Simple.Web.dll 64 | 65 | 66 | ..\packages\Simple.Web.JsonNet.0.12.1-pre\lib\net40\Simple.Web.JsonNet.dll 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | Web.config 90 | 91 | 92 | Web.config 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | {66f74977-c96f-4af0-9f98-d2ca94749649} 120 | CQRSShop.Contracts 121 | 122 | 123 | {00afe5da-93ef-4c5a-be98-006ecf0e42e6} 124 | CQRSShop.Domain 125 | 126 | 127 | {57ae018a-ba49-471d-97d7-c4ad2040d4b0} 128 | CQRSShop.Infrastructure 129 | 130 | 131 | 132 | 133 | 10.0 134 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | True 144 | True 145 | 60843 146 | / 147 | http://localhost:60843/ 148 | False 149 | False 150 | 151 | 152 | False 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | This project references NuGet package(s) that are missing on this computer. Enable NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 161 | 162 | 163 | 164 | 171 | -------------------------------------------------------------------------------- /src/CQRSShop.Web/Configuration.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | using System.Net; 3 | using EventStore.ClientAPI; 4 | using EventStore.ClientAPI.SystemData; 5 | 6 | namespace CQRSShop.Web 7 | { 8 | public static class Configuration 9 | { 10 | private static IEventStoreConnection _connection; 11 | public static IEventStoreConnection CreateConnection() 12 | { 13 | return _connection = _connection ?? Connect(); 14 | } 15 | 16 | private static IEventStoreConnection Connect() 17 | { 18 | ConnectionSettings settings = 19 | ConnectionSettings.Create() 20 | .UseConsoleLogger() 21 | .SetDefaultUserCredentials(new UserCredentials("admin", "changeit")); 22 | var endPoint = new IPEndPoint(EventStoreIP, EventStorePort); 23 | var connection = EventStoreConnection.Create(settings, endPoint, null); 24 | connection.Connect(); 25 | return connection; 26 | } 27 | 28 | public static IPAddress EventStoreIP 29 | { 30 | get 31 | { 32 | var hostname = ConfigurationManager.AppSettings["EventStoreHostName"]; 33 | if (string.IsNullOrEmpty(hostname)) 34 | { 35 | return IPAddress.Loopback; 36 | } 37 | var ipAddresses = Dns.GetHostAddresses(hostname); 38 | return ipAddresses[0]; 39 | } 40 | } 41 | 42 | public static int EventStorePort 43 | { 44 | get 45 | { 46 | var esPort = ConfigurationManager.AppSettings["EventStorePort"]; 47 | if (string.IsNullOrEmpty(esPort)) 48 | { 49 | return 1113; 50 | } 51 | return int.Parse(esPort); 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /src/CQRSShop.Web/OwinAppSetup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CQRSShop.Web; 3 | using Microsoft.Owin; 4 | using Newtonsoft.Json; 5 | using Owin; 6 | using Simple.Web; 7 | 8 | [assembly: OwinStartup(typeof(OwinAppSetup))] 9 | namespace CQRSShop.Web 10 | { 11 | public class OwinAppSetup 12 | { 13 | public static Type[] EnforceReferencesFor = 14 | { 15 | typeof (Simple.Web.JsonNet.JsonMediaTypeHandler) 16 | }; 17 | 18 | public void Configuration(IAppBuilder app) 19 | { 20 | 21 | JsonConvert.DefaultSettings = () => new JsonSerializerSettings() 22 | { 23 | TypeNameHandling = TypeNameHandling.Objects 24 | }; 25 | 26 | app.Run(context => Application.App(_ => 27 | { 28 | var task = context.Response.WriteAsync("Hello world!"); 29 | return task; 30 | })(context.Environment)); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/CQRSShop.Web/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CQRSShop.Web")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CQRSShop.Web")] 13 | [assembly: AssemblyCopyright("Copyright © 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("cd92f05e-f950-4e7c-8c89-2df867ea093e")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /src/CQRSShop.Web/Web.Debug.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 29 | 30 | -------------------------------------------------------------------------------- /src/CQRSShop.Web/Web.Release.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 17 | 18 | 19 | 30 | 31 | -------------------------------------------------------------------------------- /src/CQRSShop.Web/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/CQRSShop.Web/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/CQRSShop.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.30501.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CQRSShop.Web", "CQRSShop.Web\CQRSShop.Web.csproj", "{4E7B0EF2-E6C4-4795-8665-32D604CC8C82}" 7 | EndProject 8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "CQRSShop.Contracts", "CQRSShop.Contracts\CQRSShop.Contracts.fsproj", "{66F74977-C96F-4AF0-9F98-D2CA94749649}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{81725836-B5C6-46A2-B7E5-F546ED9C238F}" 11 | ProjectSection(SolutionItems) = preProject 12 | .nuget\NuGet.Config = .nuget\NuGet.Config 13 | .nuget\NuGet.exe = .nuget\NuGet.exe 14 | .nuget\NuGet.targets = .nuget\NuGet.targets 15 | EndProjectSection 16 | EndProject 17 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CQRSShop.Domain", "CQRSShop.Domain\CQRSShop.Domain.csproj", "{00AFE5DA-93EF-4C5A-BE98-006ECF0E42E6}" 18 | EndProject 19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CQRSShop.Infrastructure", "CQRSShop.Infrastructure\CQRSShop.Infrastructure.csproj", "{57AE018A-BA49-471D-97D7-C4AD2040D4B0}" 20 | EndProject 21 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CQRSShop.Service", "CQRSShop.Service\CQRSShop.Service.csproj", "{A78A0026-6CE5-4864-914D-07F4CDE4A4DB}" 22 | EndProject 23 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CQRSShop.Search", "CQRSShop.Search\CQRSShop.Search.csproj", "{EA9F7365-0A26-44FD-86D1-A645B5671FEA}" 24 | EndProject 25 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CQRSShop.Domain.Tests", "CQRSShop.Domain.Tests\CQRSShop.Domain.Tests.csproj", "{737BFB25-3617-437E-90D6-C15ECAD8FB25}" 26 | EndProject 27 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{5DFE6760-2FAB-499F-892B-46E2D0AD0F75}" 28 | EndProject 29 | Global 30 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 31 | Debug|Any CPU = Debug|Any CPU 32 | Release|Any CPU = Release|Any CPU 33 | EndGlobalSection 34 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 35 | {4E7B0EF2-E6C4-4795-8665-32D604CC8C82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 36 | {4E7B0EF2-E6C4-4795-8665-32D604CC8C82}.Debug|Any CPU.Build.0 = Debug|Any CPU 37 | {4E7B0EF2-E6C4-4795-8665-32D604CC8C82}.Release|Any CPU.ActiveCfg = Release|Any CPU 38 | {4E7B0EF2-E6C4-4795-8665-32D604CC8C82}.Release|Any CPU.Build.0 = Release|Any CPU 39 | {66F74977-C96F-4AF0-9F98-D2CA94749649}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {66F74977-C96F-4AF0-9F98-D2CA94749649}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {66F74977-C96F-4AF0-9F98-D2CA94749649}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {66F74977-C96F-4AF0-9F98-D2CA94749649}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {00AFE5DA-93EF-4C5A-BE98-006ECF0E42E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {00AFE5DA-93EF-4C5A-BE98-006ECF0E42E6}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {00AFE5DA-93EF-4C5A-BE98-006ECF0E42E6}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {00AFE5DA-93EF-4C5A-BE98-006ECF0E42E6}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {57AE018A-BA49-471D-97D7-C4AD2040D4B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {57AE018A-BA49-471D-97D7-C4AD2040D4B0}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {57AE018A-BA49-471D-97D7-C4AD2040D4B0}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {57AE018A-BA49-471D-97D7-C4AD2040D4B0}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {A78A0026-6CE5-4864-914D-07F4CDE4A4DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {A78A0026-6CE5-4864-914D-07F4CDE4A4DB}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {A78A0026-6CE5-4864-914D-07F4CDE4A4DB}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {A78A0026-6CE5-4864-914D-07F4CDE4A4DB}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {EA9F7365-0A26-44FD-86D1-A645B5671FEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {EA9F7365-0A26-44FD-86D1-A645B5671FEA}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {EA9F7365-0A26-44FD-86D1-A645B5671FEA}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {EA9F7365-0A26-44FD-86D1-A645B5671FEA}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {737BFB25-3617-437E-90D6-C15ECAD8FB25}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 60 | {737BFB25-3617-437E-90D6-C15ECAD8FB25}.Debug|Any CPU.Build.0 = Debug|Any CPU 61 | {737BFB25-3617-437E-90D6-C15ECAD8FB25}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {737BFB25-3617-437E-90D6-C15ECAD8FB25}.Release|Any CPU.Build.0 = Release|Any CPU 63 | EndGlobalSection 64 | GlobalSection(SolutionProperties) = preSolution 65 | HideSolutionNode = FALSE 66 | EndGlobalSection 67 | GlobalSection(NestedProjects) = preSolution 68 | {737BFB25-3617-437E-90D6-C15ECAD8FB25} = {5DFE6760-2FAB-499F-892B-46E2D0AD0F75} 69 | EndGlobalSection 70 | EndGlobal 71 | --------------------------------------------------------------------------------