├── .editorconfig ├── .gitignore ├── AllMicroServices.sln ├── Build-MicroServices.ps1 ├── BuildAndStart-MicroServices.ps1 ├── Clean-BuildArtifacts.ps1 ├── Common ├── MicroServices.Common.General │ ├── Aggregate.cs │ ├── DynamicForPrivateMembers.cs │ ├── Event.cs │ ├── Exceptions │ │ ├── AggregateDeletedException.cs │ │ ├── AggregateNotFoundException.cs │ │ ├── AggregateVersionException.cs │ │ ├── EventDeserializationException.cs │ │ └── ReadModelNotFoundException.cs │ ├── ICommand.cs │ ├── IHandle.cs │ ├── IMessage.cs │ ├── MicroServices.Common.General.csproj │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── ReadObject.cs │ ├── Util │ │ ├── ApiClient.cs │ │ ├── EventHandlerDiscovery.cs │ │ └── Try.cs │ └── packages.config ├── MicroServices.Common.sln └── MicroServices.Common │ ├── App.config │ ├── MessageBus │ ├── IMessageBus.cs │ ├── InProcessMessageBus.cs │ ├── RabbitMqBus.cs │ └── TransientSubscriber.cs │ ├── MicroServices.Common.csproj │ ├── Properties │ └── AssemblyInfo.cs │ ├── Repository │ ├── EventStoreRepository.cs │ ├── IReadModelRepository.cs │ ├── IRepository.cs │ ├── InMemoryReadModelRepository.cs │ ├── InMemoryRepository.cs │ ├── RedisReadModelRepository.cs │ └── Repository.cs │ └── packages.config ├── Invoke-MsBuild.psm1 ├── Invoke-NuGetPackageRestore.psm1 ├── Products.sln ├── Products ├── Products.Common │ ├── Dto │ │ └── ProductDto.cs │ ├── Events │ │ └── ProductEvents.cs │ ├── Products.Common.csproj │ ├── Properties │ │ └── AssemblyInfo.cs │ └── packages.config ├── Products.ReadModels.Client │ ├── IProductsView.cs │ ├── Products.ReadModels.Client.csproj │ ├── ProductsView.cs │ └── Properties │ │ └── AssemblyInfo.cs ├── Products.ReadModels.Service │ ├── App.config │ ├── App_Start │ │ └── SwaggerConfig.cs │ ├── Controllers │ │ └── ProductsController.cs │ ├── Products.ReadModels.Service.csproj │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── ServiceLocator.cs │ ├── Startup.cs │ ├── Views │ │ └── ProductView.cs │ └── packages.config ├── Products.Service.UnitTests │ ├── ProductEvents.cs │ ├── Products.Service.UnitTests.csproj │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── app.config │ └── packages.config └── Products.Service │ ├── App.config │ ├── App_Start │ └── SwaggerConfig.cs │ ├── Controllers │ └── ProductsController.cs │ ├── DataTransferObjects │ └── Commands │ │ └── Products │ │ ├── AlterProductCommand.cs │ │ └── CreateProductCommand.cs │ ├── MicroServices │ └── Products │ │ ├── Commands │ │ └── ProductCommands.cs │ │ ├── Domain │ │ └── Product.cs │ │ └── Handlers │ │ └── ProductCommandHandlers.cs │ ├── Products.Service.csproj │ ├── Program.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── ServiceLocator.cs │ ├── Startup.cs │ ├── config.yml │ └── packages.config ├── README.md ├── Reset-Data.ps1 ├── Sales.sln ├── Sales ├── Sales.Common │ ├── Dto │ │ └── OrderDto.cs │ ├── Events │ │ └── OrderEvents.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Sales.Common.csproj │ ├── app.config │ └── packages.config ├── Sales.ReadModels.Service │ ├── App.config │ ├── App_Start │ │ └── SwaggerConfig.cs │ ├── Config │ │ └── AppConfiguration.cs │ ├── Controllers │ │ └── OrdersController.cs │ ├── Program.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Sales.ReadModels.Service.csproj │ ├── ServiceLocator.cs │ ├── Startup.cs │ ├── Views │ │ └── OrderView.cs │ └── packages.config ├── Sales.Service.Tests │ ├── OrderCommandTests.cs │ ├── Products.cs │ ├── Properties │ │ └── AssemblyInfo.cs │ ├── Sales.Service.Tests.csproj │ ├── app.config │ └── packages.config └── Sales.Service │ ├── App.config │ ├── App_Start │ └── SwaggerConfig.cs │ ├── Config │ └── AppConfiguration.cs │ ├── Controllers │ └── OrdersController.cs │ ├── DataTransferObjects │ └── Commands │ │ └── OrderCommands.cs │ ├── Helpers │ └── ApiControllerHelper.cs │ ├── MicroServices │ ├── Order │ │ ├── Commands │ │ │ └── OrderCommands.cs │ │ ├── Domain │ │ │ └── Order.cs │ │ └── Handlers │ │ │ └── OrderCommandHandlers.cs │ └── Product │ │ ├── Domain │ │ └── Product.cs │ │ ├── ExternalEvents │ │ └── ProductEvents.cs │ │ ├── Handlers │ │ └── ProductEventsHandler.cs │ │ └── View │ │ └── ProductView.cs │ ├── Program.cs │ ├── Properties │ └── AssemblyInfo.cs │ ├── Sales.Service.csproj │ ├── ServiceLocator.cs │ ├── Startup.cs │ ├── config.yml │ └── packages.config ├── Start-EventStore.ps1 ├── Start-MicroServices.ps1 ├── config.yml └── redis.windows-service.conf /.editorconfig: -------------------------------------------------------------------------------- 1 | ; Top-most EditorConfig file 2 | root = true 3 | 4 | ; Windows newlines 5 | [*] 6 | end_of_line = crlf 7 | indent_style = space 8 | indent_size = 4 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # For more, see https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 2 | #OS junk files 3 | [Tt]humbs.db 4 | *.DS_Store 5 | 6 | #Visual Studio files 7 | *.[Oo]bj 8 | *.user 9 | *.aps 10 | *.pch 11 | *.vspscc 12 | *.vssscc 13 | *_i.c 14 | *_p.c 15 | *.ncb 16 | *.suo 17 | *.tlb 18 | *.tlh 19 | *.bak 20 | *.[Cc]ache 21 | *.ilk 22 | *.log 23 | *.lib 24 | *.sbr 25 | *.sdf 26 | *.opensdf 27 | *.tmp 28 | *.tmp_proj 29 | [Dd]ebug*/ 30 | [Rr]elease*/ 31 | [Dd]ebugPublic/ 32 | x64/ 33 | x86/ 34 | build/ 35 | bld/ 36 | [Bb]in/ 37 | [Oo]bj/ 38 | .vs/ 39 | 40 | .sonarqube/ 41 | sonarqube.analysis.xml 42 | 43 | *.userosscache 44 | *.sln.docstates 45 | 46 | #Tooling 47 | _ReSharper*/ 48 | *.resharper 49 | [Tt]est[Rr]esult* 50 | *.DotSettings.user 51 | *.ncrunchproject 52 | *.ncrunchsolution 53 | *_NCrunch* 54 | 55 | # TeamCity is a build add-in 56 | _TeamCity* 57 | 58 | # NuGet Packages 59 | !NuGet.exe 60 | *.nupkg 61 | # The packages folder can be ignored because of Package Restore 62 | **/packages/* 63 | # except build/, which is used as an MSBuild target. 64 | !**/packages/build/ 65 | # Uncomment if necessary however generally it will be regenerated when needed 66 | #!**/packages/repositories.config 67 | Packages*/ 68 | #Project files 69 | #[Bb]uild/ 70 | 71 | # Click-Once directory 72 | publish/ 73 | 74 | # Publish Web Output 75 | *.[Pp]ublish.xml 76 | *.azurePubxml 77 | *.pubxml 78 | *.publishproj 79 | 80 | #Subversion files 81 | .svn 82 | 83 | # Office Temp Files 84 | ~$* 85 | 86 | #VSCode 87 | +.vscode 88 | Code 89 | 90 | # User-specific files (MonoDevelop/Xamarin Studio) 91 | *.userprefs 92 | 93 | # DNX 94 | project.lock.json 95 | artifacts/ 96 | 97 | # Chutzpah Test files 98 | _Chutzpah* 99 | 100 | # Visual Studio profiler 101 | *.psess 102 | *.vsp 103 | *.vspx 104 | *.sap 105 | 106 | # SQL Server files 107 | *.mdf 108 | *.ldf 109 | -------------------------------------------------------------------------------- /AllMicroServices.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2009 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicroServices.Common.General", "Common\MicroServices.Common.General\MicroServices.Common.General.csproj", "{FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicroServices.Common", "Common\MicroServices.Common\MicroServices.Common.csproj", "{D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ProductsMicroservices", "ProductsMicroservices", "{7A4D06AA-EA6A-4396-9064-99769363F7F1}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SalesMicroservices", "SalesMicroservices", "{3F07AA16-72DD-4BCA-9F4A-3FDEBF731B48}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{F4108442-FC95-4BD1-ABE8-CB2FEABD11E2}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReadModelService", "ReadModelService", "{D24244B0-22AD-4E36-9696-07776C767176}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DomainService", "DomainService", "{13B91778-5F61-4660-A803-5E0904F36FE0}" 19 | EndProject 20 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{07A78D34-9A61-4A86-8A66-3C2ACA4C2BB9}" 21 | EndProject 22 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DomainService", "DomainService", "{682779B6-B21E-4239-A4F1-A813E5FBC06A}" 23 | EndProject 24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReadModelService", "ReadModelService", "{938B459B-2647-4BA5-9869-C767AFACBF5C}" 25 | EndProject 26 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Products.Common", "Products\Products.Common\Products.Common.csproj", "{5E571774-1D77-4F79-AF04-27A85DAC1340}" 27 | EndProject 28 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Products.Service", "Products\Products.Service\Products.Service.csproj", "{4EF4FA1A-483A-4F5C-9A98-012BE17B7672}" 29 | EndProject 30 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Products.Service.UnitTests", "Products\Products.Service.UnitTests\Products.Service.UnitTests.csproj", "{0C3E7514-3CA5-4FF5-BD42-F19EF1A4783D}" 31 | EndProject 32 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Products.ReadModels.Service", "Products\Products.ReadModels.Service\Products.ReadModels.Service.csproj", "{D91AEECC-CA6A-48A2-90D6-5146D945DEAB}" 33 | EndProject 34 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Products.ReadModels.Client", "Products\Products.ReadModels.Client\Products.ReadModels.Client.csproj", "{C46D3E71-E08D-4F45-B54B-03E3759028E3}" 35 | EndProject 36 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sales.Common", "Sales\Sales.Common\Sales.Common.csproj", "{D3478255-CB14-4F5D-AC78-2E48B5EC2439}" 37 | EndProject 38 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sales.Service", "Sales\Sales.Service\Sales.Service.csproj", "{2D8DF944-BAC5-4CE1-807A-6FFCE3C51284}" 39 | EndProject 40 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sales.Service.Tests", "Sales\Sales.Service.Tests\Sales.Service.Tests.csproj", "{FDED84A3-EC77-42E1-BDAA-E5A2A56E800F}" 41 | EndProject 42 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sales.ReadModels.Service", "Sales\Sales.ReadModels.Service\Sales.ReadModels.Service.csproj", "{B1ECFB12-A337-49FF-AD09-304B5292A548}" 43 | EndProject 44 | Global 45 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 46 | Debug|Any CPU = Debug|Any CPU 47 | Release|Any CPU = Release|Any CPU 48 | EndGlobalSection 49 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 50 | {FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {5E571774-1D77-4F79-AF04-27A85DAC1340}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {5E571774-1D77-4F79-AF04-27A85DAC1340}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {5E571774-1D77-4F79-AF04-27A85DAC1340}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {5E571774-1D77-4F79-AF04-27A85DAC1340}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {4EF4FA1A-483A-4F5C-9A98-012BE17B7672}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {4EF4FA1A-483A-4F5C-9A98-012BE17B7672}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {4EF4FA1A-483A-4F5C-9A98-012BE17B7672}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {4EF4FA1A-483A-4F5C-9A98-012BE17B7672}.Release|Any CPU.Build.0 = Release|Any CPU 66 | {0C3E7514-3CA5-4FF5-BD42-F19EF1A4783D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {0C3E7514-3CA5-4FF5-BD42-F19EF1A4783D}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {0C3E7514-3CA5-4FF5-BD42-F19EF1A4783D}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {0C3E7514-3CA5-4FF5-BD42-F19EF1A4783D}.Release|Any CPU.Build.0 = Release|Any CPU 70 | {D91AEECC-CA6A-48A2-90D6-5146D945DEAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 71 | {D91AEECC-CA6A-48A2-90D6-5146D945DEAB}.Debug|Any CPU.Build.0 = Debug|Any CPU 72 | {D91AEECC-CA6A-48A2-90D6-5146D945DEAB}.Release|Any CPU.ActiveCfg = Release|Any CPU 73 | {D91AEECC-CA6A-48A2-90D6-5146D945DEAB}.Release|Any CPU.Build.0 = Release|Any CPU 74 | {C46D3E71-E08D-4F45-B54B-03E3759028E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 75 | {C46D3E71-E08D-4F45-B54B-03E3759028E3}.Debug|Any CPU.Build.0 = Debug|Any CPU 76 | {C46D3E71-E08D-4F45-B54B-03E3759028E3}.Release|Any CPU.ActiveCfg = Release|Any CPU 77 | {C46D3E71-E08D-4F45-B54B-03E3759028E3}.Release|Any CPU.Build.0 = Release|Any CPU 78 | {D3478255-CB14-4F5D-AC78-2E48B5EC2439}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 79 | {D3478255-CB14-4F5D-AC78-2E48B5EC2439}.Debug|Any CPU.Build.0 = Debug|Any CPU 80 | {D3478255-CB14-4F5D-AC78-2E48B5EC2439}.Release|Any CPU.ActiveCfg = Release|Any CPU 81 | {D3478255-CB14-4F5D-AC78-2E48B5EC2439}.Release|Any CPU.Build.0 = Release|Any CPU 82 | {2D8DF944-BAC5-4CE1-807A-6FFCE3C51284}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 83 | {2D8DF944-BAC5-4CE1-807A-6FFCE3C51284}.Debug|Any CPU.Build.0 = Debug|Any CPU 84 | {2D8DF944-BAC5-4CE1-807A-6FFCE3C51284}.Release|Any CPU.ActiveCfg = Release|Any CPU 85 | {2D8DF944-BAC5-4CE1-807A-6FFCE3C51284}.Release|Any CPU.Build.0 = Release|Any CPU 86 | {FDED84A3-EC77-42E1-BDAA-E5A2A56E800F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 87 | {FDED84A3-EC77-42E1-BDAA-E5A2A56E800F}.Debug|Any CPU.Build.0 = Debug|Any CPU 88 | {FDED84A3-EC77-42E1-BDAA-E5A2A56E800F}.Release|Any CPU.ActiveCfg = Release|Any CPU 89 | {FDED84A3-EC77-42E1-BDAA-E5A2A56E800F}.Release|Any CPU.Build.0 = Release|Any CPU 90 | {B1ECFB12-A337-49FF-AD09-304B5292A548}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 91 | {B1ECFB12-A337-49FF-AD09-304B5292A548}.Debug|Any CPU.Build.0 = Debug|Any CPU 92 | {B1ECFB12-A337-49FF-AD09-304B5292A548}.Release|Any CPU.ActiveCfg = Release|Any CPU 93 | {B1ECFB12-A337-49FF-AD09-304B5292A548}.Release|Any CPU.Build.0 = Release|Any CPU 94 | EndGlobalSection 95 | GlobalSection(SolutionProperties) = preSolution 96 | HideSolutionNode = FALSE 97 | EndGlobalSection 98 | GlobalSection(NestedProjects) = preSolution 99 | {F4108442-FC95-4BD1-ABE8-CB2FEABD11E2} = {7A4D06AA-EA6A-4396-9064-99769363F7F1} 100 | {D24244B0-22AD-4E36-9696-07776C767176} = {7A4D06AA-EA6A-4396-9064-99769363F7F1} 101 | {13B91778-5F61-4660-A803-5E0904F36FE0} = {7A4D06AA-EA6A-4396-9064-99769363F7F1} 102 | {07A78D34-9A61-4A86-8A66-3C2ACA4C2BB9} = {3F07AA16-72DD-4BCA-9F4A-3FDEBF731B48} 103 | {682779B6-B21E-4239-A4F1-A813E5FBC06A} = {3F07AA16-72DD-4BCA-9F4A-3FDEBF731B48} 104 | {938B459B-2647-4BA5-9869-C767AFACBF5C} = {3F07AA16-72DD-4BCA-9F4A-3FDEBF731B48} 105 | {5E571774-1D77-4F79-AF04-27A85DAC1340} = {F4108442-FC95-4BD1-ABE8-CB2FEABD11E2} 106 | {4EF4FA1A-483A-4F5C-9A98-012BE17B7672} = {13B91778-5F61-4660-A803-5E0904F36FE0} 107 | {0C3E7514-3CA5-4FF5-BD42-F19EF1A4783D} = {13B91778-5F61-4660-A803-5E0904F36FE0} 108 | {D91AEECC-CA6A-48A2-90D6-5146D945DEAB} = {D24244B0-22AD-4E36-9696-07776C767176} 109 | {C46D3E71-E08D-4F45-B54B-03E3759028E3} = {D24244B0-22AD-4E36-9696-07776C767176} 110 | {D3478255-CB14-4F5D-AC78-2E48B5EC2439} = {07A78D34-9A61-4A86-8A66-3C2ACA4C2BB9} 111 | {2D8DF944-BAC5-4CE1-807A-6FFCE3C51284} = {682779B6-B21E-4239-A4F1-A813E5FBC06A} 112 | {FDED84A3-EC77-42E1-BDAA-E5A2A56E800F} = {682779B6-B21E-4239-A4F1-A813E5FBC06A} 113 | {B1ECFB12-A337-49FF-AD09-304B5292A548} = {938B459B-2647-4BA5-9869-C767AFACBF5C} 114 | EndGlobalSection 115 | GlobalSection(ExtensibilityGlobals) = postSolution 116 | SolutionGuid = {821DEB2E-922C-4FC9-9BF2-D45E9FEA13AB} 117 | EndGlobalSection 118 | EndGlobal 119 | -------------------------------------------------------------------------------- /Build-MicroServices.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | param () 3 | 4 | Import-Module -Name "$PSScriptRoot\Invoke-MsBuild.psm1" 5 | Import-Module -Name "$PSScriptRoot\Invoke-NuGetPackageRestore.psm1" 6 | 7 | $solutions = @( 8 | @{ Solution = "Microservices Common"; Path = "$PSScriptRoot\MicroService-Common\MicroServices.Common.sln" }, 9 | @{ Solution = "Admin"; Path = "$PSScriptRoot\Admin\Admin.sln" }, 10 | @{ Solution = "Cashier"; Path = "$PSScriptRoot\Cashier\Cashier.sln" }, 11 | @{ Solution = "Barista"; Path = "$PSScriptRoot\Barista\Barista.sln" } 12 | ) 13 | 14 | Function ReportProgress($currentSolution, $currentStatus, $currentPercentage) { 15 | Write-Progress -Id 1 -Activity "Microservices Build" -Status "Building $currentSolution" -CurrentOperation $currentStatus -PercentComplete $currentPercentage 16 | } 17 | 18 | Function BuildSolution($pathToSolution, $solutionName, $alreadyComplete, $totalSteps) { 19 | $currentProgress = $alreadyComplete / $totalSteps * 100 20 | $progressStep = 1 / $totalSteps * 100 / 2 21 | 22 | ReportProgress $solutionName "Restoring NuGet packages" $currentProgress 23 | $nugetSucceeded = Invoke-NuGetPackageRestore -Path $pathToSolution 24 | 25 | if (!$nugetSucceeded) 26 | { 27 | return $nugetSucceeded 28 | } 29 | 30 | ReportProgress $solutionName "Running MSBuild" ($currentProgress + $progressStep) 31 | $buildSucceeded = Invoke-MsBuild -Path $pathToSolution 32 | 33 | return $buildSucceeded; 34 | } 35 | 36 | [System.Collections.ArrayList]$results = @() 37 | 38 | Function AddResult($solution, $result) { 39 | $object = New-Object -TypeName PSObject 40 | 41 | $object | Add-Member -MemberType NoteProperty -Name "Result" -Value $result 42 | $object | Add-Member -MemberType NoteProperty -Name "Solution" -Value $solution.Solution 43 | $object | Add-Member -MemberType NoteProperty -Name "Location" -Value $solution.Path 44 | 45 | $results.Add($object) | Out-Null 46 | } 47 | 48 | foreach ($solution in $solutions) { 49 | if (($results | Where-Object { $_.Result -eq "Failed" }) -ne $null) { 50 | AddResult $solution "Skipped" 51 | continue 52 | } 53 | 54 | $success = BuildSolution $solution.Path $solution.Solution $solutions.IndexOf($solution) $solutions.Length 55 | 56 | if ($success) { 57 | AddResult $solution "Succeeded" 58 | } 59 | else { 60 | AddResult $solution "Failed" 61 | } 62 | } 63 | 64 | return $results -------------------------------------------------------------------------------- /BuildAndStart-MicroServices.ps1: -------------------------------------------------------------------------------- 1 | Invoke-Expression .\build-microservices.ps1 2 | Invoke-Expression .\start-microservices.ps1 -------------------------------------------------------------------------------- /Clean-BuildArtifacts.ps1: -------------------------------------------------------------------------------- 1 | Write-Host "Cleaning the bin directories" 2 | get-childitem "C:\Projects\Microservices\microservice-poc" -include *.* -recurse | where-object { $_.fullname -like "*\bin\*" } | foreach ($_) { remove-item $_.fullname -recurse -Confirm:$false } 3 | 4 | Write-Host "Cleaning the obj directories" 5 | get-childitem "C:\Projects\Microservices\microservice-poc" -include *.* -recurse | where-object { $_.fullname -like "*\obj\*" } | foreach ($_) { remove-item $_.fullname -recurse -Confirm:$false } 6 | 7 | Write-Host "Cleaning the packages directories" 8 | get-childitem "C:\Projects\Microservices\microservice-poc" -include *.* -recurse | where-object { $_.fullname -like "*\packages\*" } | foreach ($_) { remove-item $_.fullname -recurse -Confirm:$false } 9 | 10 | 11 | Write-Host "Press any key to continue ..." 12 | $x = $host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") -------------------------------------------------------------------------------- /Common/MicroServices.Common.General/Aggregate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MicroServices.Common 5 | { 6 | public abstract class Aggregate 7 | { 8 | internal readonly List events = new List(); 9 | 10 | public Guid Id { get; protected set; } 11 | 12 | private int version = -1; 13 | public int Version { get { return version; } internal set { version = value; } } 14 | 15 | public IEnumerable GetUncommittedEvents() 16 | { 17 | return events; 18 | } 19 | 20 | public void MarkEventsAsCommitted() 21 | { 22 | events.Clear(); 23 | } 24 | 25 | public void LoadStateFromHistory(IEnumerable history) 26 | { 27 | foreach (var e in history) ApplyEvent(e, false); 28 | } 29 | 30 | protected internal void ApplyEvent(Event @event) 31 | { 32 | ApplyEvent(@event, true); 33 | } 34 | 35 | protected virtual void ApplyEvent(Event @event, bool isNew) 36 | { 37 | this.AsDynamic().Apply(@event); 38 | if (isNew) 39 | { 40 | @event.Version = ++Version; 41 | events.Add(@event); 42 | } 43 | else 44 | { 45 | Version = @event.Version; 46 | } 47 | } 48 | } 49 | 50 | public abstract class ReadModelAggregate : Aggregate 51 | { 52 | protected internal void ApplyEvent(Event @event, int version) 53 | { 54 | @event.Version = version; 55 | ApplyEvent(@event, true); 56 | } 57 | protected override void ApplyEvent(Event @event, bool isNew) 58 | { 59 | this.AsDynamic().Apply(@event); 60 | if (isNew) 61 | { 62 | events.Add(@event); 63 | } 64 | Version++; 65 | 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /Common/MicroServices.Common.General/Event.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MicroServices.Common 4 | { 5 | public interface IEvent : IMessage 6 | { 7 | Guid Id { get; } 8 | int Version { get; } 9 | } 10 | 11 | public class Event : IEvent 12 | { 13 | public Guid Id { get; protected set; } 14 | public int Version { get; internal protected set; } 15 | } 16 | } -------------------------------------------------------------------------------- /Common/MicroServices.Common.General/Exceptions/AggregateDeletedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MicroServices.Common.Exceptions 8 | { 9 | public class AggregateDeletedException : Exception 10 | { 11 | public readonly Guid Id; 12 | public readonly Type Type; 13 | 14 | public AggregateDeletedException(Guid id, Type type) 15 | : base(string.Format("Aggregate '{0}' (type {1}) was deleted.", id, type.Name)) 16 | { 17 | Id = id; 18 | Type = type; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Common/MicroServices.Common.General/Exceptions/AggregateNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MicroServices.Common.Exceptions 8 | { 9 | public class AggregateNotFoundException : Exception 10 | { 11 | public readonly Guid Id; 12 | public readonly Type Type; 13 | 14 | public AggregateNotFoundException(Guid id, Type type) 15 | : base(string.Format("Aggregate '{0}' (type {1}) was not found.", id, type.Name)) 16 | { 17 | Id = id; 18 | Type = type; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Common/MicroServices.Common.General/Exceptions/AggregateVersionException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MicroServices.Common.Exceptions 8 | { 9 | public class AggregateVersionException : Exception 10 | { 11 | public readonly Guid Id; 12 | public readonly Type Type; 13 | public readonly int AggregateVersion; 14 | public readonly int RequestedVersion; 15 | 16 | public AggregateVersionException(Guid id, Type type, int aggregateVersion, int requestedVersion) 17 | : base(string.Format("Requested version {2} of aggregate '{0}' (type {1}) - aggregate version is {3}", id, type.Name, requestedVersion, aggregateVersion)) 18 | { 19 | Id = id; 20 | Type = type; 21 | AggregateVersion = aggregateVersion; 22 | RequestedVersion = requestedVersion; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Common/MicroServices.Common.General/Exceptions/EventDeserializationException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MicroServices.Common.Exceptions 8 | { 9 | public class EventDeserializationException : Exception 10 | { 11 | public readonly string EventTypeName; 12 | public readonly string Metadata; 13 | 14 | public EventDeserializationException(string eventTypeName, string metadata) 15 | : base(string.Format("Could not deserialize {0} as an Event (metadata: {1})", eventTypeName, metadata)) 16 | { 17 | EventTypeName = eventTypeName; 18 | Metadata = metadata; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Common/MicroServices.Common.General/Exceptions/ReadModelNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MicroServices.Common.Exceptions 8 | { 9 | public class ReadModelNotFoundException : Exception 10 | { 11 | public readonly Guid Id; 12 | public readonly Type Type; 13 | 14 | public ReadModelNotFoundException(Guid id, Type type) 15 | : base(string.Format("ReadModel '{0}' (type {1}) was not found.", id, type.Name)) 16 | { 17 | Id = id; 18 | Type = type; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Common/MicroServices.Common.General/ICommand.cs: -------------------------------------------------------------------------------- 1 | namespace MicroServices.Common 2 | { 3 | public interface ICommand : IMessage 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Common/MicroServices.Common.General/IHandle.cs: -------------------------------------------------------------------------------- 1 | namespace MicroServices.Common 2 | { 3 | public interface IHandle where T:Event 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /Common/MicroServices.Common.General/IMessage.cs: -------------------------------------------------------------------------------- 1 | namespace MicroServices.Common 2 | { 3 | public interface IMessage 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Common/MicroServices.Common.General/MicroServices.Common.General.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11} 8 | Library 9 | Properties 10 | MicroServices.Common.General 11 | MicroServices.Common.General 12 | v4.6.2 13 | 512 14 | ..\..\MasterData-Service\ 15 | 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 | ..\..\PullRequest\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll 37 | 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 | 66 | 67 | 68 | 69 | 76 | -------------------------------------------------------------------------------- /Common/MicroServices.Common.General/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("MicroServices.Contracts")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("MicroServices.Contracts")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 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("fc24d38f-fb96-43e1-b21e-b6e1bfa1dc11")] 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 | -------------------------------------------------------------------------------- /Common/MicroServices.Common.General/ReadObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace MicroServices.Common 4 | { 5 | public abstract class ReadObject 6 | { 7 | public Guid Id { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /Common/MicroServices.Common.General/Util/ApiClient.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | 10 | namespace MicroServices.Common.General.Util 11 | { 12 | public class ApiClient 13 | { 14 | public T Load(string url) 15 | { 16 | var webRequest = WebRequest.Create(url); 17 | webRequest.ContentType = "text/json"; 18 | webRequest.Method = "GET"; 19 | 20 | T result = default(T); 21 | 22 | var response = webRequest.GetResponse(); 23 | using (var streamReader = new StreamReader(response.GetResponseStream())) 24 | { 25 | var jsonString = streamReader.ReadToEnd(); 26 | result = JsonConvert.DeserializeObject(jsonString); 27 | } 28 | 29 | return result; 30 | } 31 | 32 | public IList LoadMany(string url) 33 | { 34 | return Load>(url); 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Common/MicroServices.Common.General/Util/EventHandlerDiscovery.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace MicroServices.Common.General.Util 9 | { 10 | public class EventHandlerDiscovery 11 | { 12 | public Dictionary Handlers 13 | { 14 | get; private set; 15 | } 16 | 17 | public EventHandlerDiscovery() 18 | { 19 | Handlers = new Dictionary(); 20 | } 21 | 22 | public EventHandlerDiscovery Scan(Aggregate aggregate) 23 | { 24 | var handlerInterface = typeof(IHandle<>); 25 | var aggType = aggregate.GetType(); 26 | 27 | var interfaces = aggType.GetInterfaces(); 28 | 29 | var instances = from i in aggType.GetInterfaces() 30 | where (i.IsGenericType && handlerInterface.IsAssignableFrom(i.GetGenericTypeDefinition())) 31 | select i.GenericTypeArguments[0]; 32 | 33 | foreach (var i in instances) 34 | { 35 | Handlers.Add(i, aggregate); 36 | } 37 | 38 | return this; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Common/MicroServices.Common.General/Util/Try.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace MicroServices.Common.General.Util 8 | { 9 | public class Try 10 | { 11 | protected Action Action {get; set;} 12 | 13 | private int maxTries = 1; 14 | private Action onFailure; 15 | 16 | protected Try(Action action) 17 | { 18 | Action = action; 19 | } 20 | 21 | public static Try To(Action action) 22 | { 23 | return new Try(action); 24 | } 25 | 26 | public Try OnFailedAttempt(Action action) 27 | { 28 | onFailure = action; 29 | return this; 30 | } 31 | 32 | protected virtual TryResult Attempt() 33 | { 34 | for (int attempt = 1; attempt <= maxTries; attempt++) 35 | { 36 | try 37 | { 38 | Action(); 39 | break; 40 | } 41 | catch (Exception ex) 42 | { 43 | if (onFailure != null) onFailure(); 44 | if (attempt < maxTries) continue; 45 | 46 | return new TryResult(ex); 47 | } 48 | } 49 | 50 | return new TryResult(true); 51 | } 52 | 53 | public Try UpTo(int maxAttempts) 54 | { 55 | maxTries = maxAttempts; 56 | return this; 57 | } 58 | 59 | public TryResult Times() 60 | { 61 | return Attempt(); 62 | } 63 | } 64 | 65 | public class TryResult 66 | { 67 | public TryResult(bool succeeded) 68 | { 69 | Succeeded = succeeded; 70 | } 71 | 72 | public TryResult(Exception exception) 73 | { 74 | Succeeded = false; 75 | Exception = exception; 76 | } 77 | 78 | public bool Succeeded { get; private set; } 79 | public Exception Exception { get; private set; } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Common/MicroServices.Common.General/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /Common/MicroServices.Common.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.23107.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicroServices.Common", "MicroServices.Common\MicroServices.Common.csproj", "{D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicroServices.Common.General", "MicroServices.Common.General\MicroServices.Common.General.csproj", "{FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /Common/MicroServices.Common/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Common/MicroServices.Common/MessageBus/IMessageBus.cs: -------------------------------------------------------------------------------- 1 | namespace MicroServices.Common.MessageBus 2 | { 3 | public interface IMessageBus 4 | { 5 | void Publish(T @event) where T : Event; 6 | void Send(T command) where T : class, ICommand; 7 | } 8 | } -------------------------------------------------------------------------------- /Common/MicroServices.Common/MessageBus/InProcessMessageBus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | 5 | namespace MicroServices.Common.MessageBus 6 | { 7 | public class InProcessMessageBus : IMessageBus 8 | { 9 | private readonly Dictionary>> commandHandlers = new Dictionary>>(); 10 | 11 | //Registers handlers for both events and commands 12 | public void RegisterHandler(Action handler) where T : IMessage 13 | { 14 | List> handlers; 15 | 16 | if (!commandHandlers.TryGetValue(typeof(T), out handlers)) 17 | { 18 | handlers = new List>(); 19 | commandHandlers.Add(typeof(T), handlers); 20 | } 21 | 22 | handlers.Add((msg => handler((T)msg))); 23 | } 24 | 25 | public void Publish(T @event) where T : Event 26 | { 27 | List> handlers; 28 | 29 | if (!commandHandlers.TryGetValue(@event.GetType(), out handlers)) return; 30 | 31 | foreach (var handler in handlers) 32 | { 33 | //dispatch on thread pool for added awesomeness 34 | var handler1 = handler; 35 | ThreadPool.QueueUserWorkItem(x => handler1(@event)); 36 | } 37 | } 38 | 39 | //the inproc message bus only sends command messages to one handler 40 | public void Send(T command) where T : class,ICommand 41 | { 42 | List> matchingHandlers; 43 | 44 | if (commandHandlers.TryGetValue(typeof(T), out matchingHandlers)) 45 | { 46 | if (matchingHandlers.Count != 1) throw new InvalidOperationException("cannot send commands to more than one handler"); 47 | matchingHandlers[0](command); 48 | } 49 | else 50 | { 51 | throw new InvalidOperationException("no command handler registered"); 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Common/MicroServices.Common/MessageBus/RabbitMqBus.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using EasyNetQ; 7 | using Newtonsoft.Json; 8 | 9 | namespace MicroServices.Common.MessageBus 10 | { 11 | public class RabbitMqBus : IMessageBus 12 | { 13 | private readonly IBus bus; 14 | 15 | public IBus Bus { get { return bus; } } 16 | 17 | public RabbitMqBus(IBus easyNetQBus) 18 | { 19 | if (easyNetQBus == null) 20 | { 21 | throw new ArgumentNullException("easyNetQBus"); 22 | } 23 | bus = easyNetQBus; 24 | } 25 | 26 | public void Publish(T @event) where T : Event 27 | { 28 | if (bus != null) 29 | { 30 | var innerMessage = JsonConvert.SerializeObject(@event); 31 | 32 | var eventType = @event.GetType(); 33 | var typeName = eventType.ToString(); 34 | var topicName = typeName.Substring(0, typeName.LastIndexOf(".")); 35 | 36 | var message = new PublishedMessage() { MessageTypeName = eventType.AssemblyQualifiedName, SerialisedMessage = innerMessage }; 37 | 38 | bus.PublishAsync(message, topicName).Wait(); 39 | } 40 | else 41 | { 42 | throw new ApplicationException("RabbitMqBus is not yet Initialized"); 43 | } 44 | } 45 | 46 | void IMessageBus.Send(T command) 47 | { 48 | throw new NotImplementedException(); 49 | } 50 | } 51 | 52 | public class PublishedMessage : IMessage 53 | { 54 | public string MessageTypeName { get; set; } 55 | public string SerialisedMessage { get; set; } 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Common/MicroServices.Common/MessageBus/TransientSubscriber.cs: -------------------------------------------------------------------------------- 1 | using EasyNetQ; 2 | using System; 3 | 4 | namespace MicroServices.Common.MessageBus 5 | { 6 | public class TransientSubscriber : IDisposable 7 | { 8 | private ISubscriptionResult subscription; 9 | 10 | private readonly Action handler; 11 | private readonly string topic; 12 | private readonly string listenerName; 13 | 14 | public TransientSubscriber(string listenerName, string topic, Action handler) 15 | { 16 | this.listenerName = listenerName; 17 | this.topic = topic; 18 | this.handler = handler; 19 | 20 | InitializeBus(); 21 | } 22 | 23 | private void InitializeBus() 24 | { 25 | var bus = RabbitHutch.CreateBus("host=localhost"); 26 | subscription = bus.Subscribe(listenerName, m => handler(), q => q.WithTopic(topic)); 27 | } 28 | 29 | public void Dispose() 30 | { 31 | subscription.Dispose(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Common/MicroServices.Common/MicroServices.Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {D3D97918-2C26-4BAE-A0C5-F90B6F1085DD} 8 | Library 9 | Properties 10 | MicroServices.Common 11 | MicroServices.Common 12 | v4.6.2 13 | 512 14 | 15 | ..\ 16 | true 17 | 18 | 19 | true 20 | full 21 | false 22 | bin\Debug\ 23 | DEBUG;TRACE 24 | prompt 25 | 4 26 | 27 | 28 | pdbonly 29 | true 30 | bin\Release\ 31 | TRACE 32 | prompt 33 | 4 34 | 35 | 36 | 37 | ..\..\PullRequest\packages\EasyNetQ.2.2.0\lib\net451\EasyNetQ.dll 38 | 39 | 40 | ..\..\PullRequest\packages\EventStore.Client.4.0.3\lib\net40\EventStore.ClientAPI.dll 41 | 42 | 43 | ..\..\PullRequest\packages\Microsoft.Diagnostics.Tracing.EventSource.Redist.1.1.28\lib\net46\Microsoft.Diagnostics.Tracing.EventSource.dll 44 | 45 | 46 | ..\..\PullRequest\packages\NewId.3.0.1\lib\net452\NewId.dll 47 | 48 | 49 | ..\..\PullRequest\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll 50 | 51 | 52 | ..\..\PullRequest\packages\RabbitMQ.Client.5.0.1\lib\net451\RabbitMQ.Client.dll 53 | 54 | 55 | ..\..\PullRequest\packages\StackExchange.Redis.1.2.6\lib\net46\StackExchange.Redis.dll 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | {fc24d38f-fb96-43e1-b21e-b6e1bfa1dc11} 89 | MicroServices.Common.General 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 104 | -------------------------------------------------------------------------------- /Common/MicroServices.Common/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("MicroServices.Common")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("MicroServices.Common")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 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("d3d97918-2c26-4bae-a0c5-f90b6f1085dd")] 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 | -------------------------------------------------------------------------------- /Common/MicroServices.Common/Repository/EventStoreRepository.cs: -------------------------------------------------------------------------------- 1 | using EventStore.ClientAPI; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using MicroServices.Common.Exceptions; 5 | using MicroServices.Common.MessageBus; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | 12 | namespace MicroServices.Common.Repository 13 | { 14 | public class EventStoreRepository : IRepository 15 | { 16 | private const string EventClrTypeHeader = "EventClrTypeName"; 17 | private const string AggregateClrTypeHeader = "AggregateClrTypeName"; 18 | private const string CommitIdHeader = "CommitId"; 19 | private const int WritePageSize = 500; 20 | private const int ReadPageSize = 500; 21 | 22 | private static readonly JsonSerializerSettings serializationSettings; 23 | private readonly IEventStoreConnection eventStoreConnection; 24 | private readonly IMessageBus bus; 25 | 26 | static EventStoreRepository() 27 | { 28 | serializationSettings = new JsonSerializerSettings 29 | { 30 | TypeNameHandling = TypeNameHandling.None 31 | }; 32 | } 33 | 34 | public EventStoreRepository(IEventStoreConnection eventStoreConnection, IMessageBus bus) 35 | { 36 | this.eventStoreConnection = eventStoreConnection; 37 | this.bus = bus; 38 | } 39 | 40 | public TAggregate GetById(Guid id) where TAggregate : Aggregate 41 | { 42 | return GetByIdAsync(id).Result; 43 | } 44 | 45 | public async Task GetByIdAsync(Guid id) where TAggregate : Aggregate 46 | { 47 | return await GetByIdAsync(id, int.MaxValue); 48 | } 49 | 50 | public async Task GetByIdAsync(Guid id, int version) where TAggregate : Aggregate 51 | { 52 | if (version <= 0) 53 | throw new InvalidOperationException("Cannot get version <= 0"); 54 | 55 | var streamName = AggregateIdToStreamName(typeof(TAggregate), id); 56 | var aggregate = ConstructAggregate(); 57 | 58 | int sliceStart = 0; 59 | StreamEventsSlice currentSlice; 60 | do 61 | { 62 | int sliceCount = sliceStart + ReadPageSize <= version 63 | ? ReadPageSize 64 | : version - sliceStart + 1; 65 | 66 | currentSlice = await eventStoreConnection.ReadStreamEventsForwardAsync(streamName, sliceStart, sliceCount, false); 67 | 68 | if (currentSlice.Status == SliceReadStatus.StreamNotFound) 69 | throw new AggregateNotFoundException(id, typeof(TAggregate)); 70 | 71 | if (currentSlice.Status == SliceReadStatus.StreamDeleted) 72 | throw new AggregateDeletedException(id, typeof(TAggregate)); 73 | 74 | sliceStart = (int)currentSlice.NextEventNumber; 75 | 76 | var history = new List(); 77 | foreach (var evnt in currentSlice.Events) 78 | history.Add(DeserializeEvent(evnt.OriginalEvent.Metadata, evnt.OriginalEvent.Data)); 79 | aggregate.LoadStateFromHistory(history); 80 | } while (version >= currentSlice.NextEventNumber && !currentSlice.IsEndOfStream); 81 | 82 | if (aggregate.Version != version && version < Int32.MaxValue) 83 | throw new AggregateVersionException(id, typeof(TAggregate), aggregate.Version, version); 84 | 85 | return aggregate; 86 | } 87 | 88 | private static TAggregate ConstructAggregate() 89 | { 90 | return (TAggregate)Activator.CreateInstance(typeof(TAggregate), true); 91 | } 92 | 93 | private static Event DeserializeEvent(byte[] metadata, byte[] data) 94 | { 95 | var metadataString = Encoding.UTF8.GetString(metadata); 96 | var eventClrTypeName = JObject.Parse(metadataString).Property(EventClrTypeHeader).Value; 97 | var @event = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(data), Type.GetType((string)eventClrTypeName)); 98 | if (@event as Event == null) 99 | { 100 | throw new EventDeserializationException((string)eventClrTypeName, metadataString); 101 | } 102 | return @event as Event; 103 | } 104 | 105 | public void Save(TAggregate aggregate) where TAggregate : Aggregate 106 | { 107 | SaveAsync(aggregate).Wait(); 108 | } 109 | 110 | public async Task SaveAsync(TAggregate aggregate) where TAggregate : Aggregate 111 | { 112 | var commitHeaders = new Dictionary 113 | { 114 | {CommitIdHeader, aggregate.Id}, 115 | {AggregateClrTypeHeader, aggregate.GetType().AssemblyQualifiedName} 116 | }; 117 | 118 | var streamName = AggregateIdToStreamName(aggregate.GetType(), aggregate.Id); 119 | var eventsToPublish = aggregate.GetUncommittedEvents(); 120 | var newEvents = eventsToPublish.Cast().ToList(); 121 | var originalVersion = aggregate.Version - newEvents.Count; 122 | var expectedVersion = originalVersion == -1 ? ExpectedVersion.NoStream : originalVersion; 123 | var eventsToSave = newEvents.Select(e => ToEventData(Guid.NewGuid(), e, commitHeaders)).ToList(); 124 | 125 | if (eventsToSave.Count < WritePageSize) 126 | { 127 | await eventStoreConnection.AppendToStreamAsync(streamName, expectedVersion, eventsToSave); 128 | } 129 | else 130 | { 131 | var transaction = await eventStoreConnection.StartTransactionAsync(streamName, expectedVersion); 132 | 133 | var position = 0; 134 | while (position < eventsToSave.Count) 135 | { 136 | var pageEvents = eventsToSave.Skip(position).Take(WritePageSize); 137 | await transaction.WriteAsync(pageEvents); 138 | position += WritePageSize; 139 | } 140 | 141 | await transaction.CommitAsync(); 142 | } 143 | 144 | if (bus != null) 145 | { 146 | foreach (var e in eventsToPublish) 147 | { 148 | bus.Publish(e); 149 | } 150 | } 151 | 152 | aggregate.MarkEventsAsCommitted(); 153 | } 154 | 155 | private string AggregateIdToStreamName(Type type, Guid id) 156 | { 157 | //Ensure first character of type name is lower case to follow javascript naming conventions 158 | return string.Format("{0}-{1}", char.ToLower(type.Name[0]) + type.Name.Substring(1), id.ToString("N")); 159 | } 160 | 161 | private static EventData ToEventData(Guid eventId, object @event, IDictionary headers) 162 | { 163 | var data = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(@event, serializationSettings)); 164 | 165 | var eventHeaders = new Dictionary(headers) 166 | { 167 | { 168 | EventClrTypeHeader, @event.GetType().AssemblyQualifiedName 169 | } 170 | }; 171 | var metadata = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(eventHeaders, serializationSettings)); 172 | var typeName = @event.GetType().Name; 173 | 174 | return new EventData(eventId, typeName, true, data, metadata); 175 | } 176 | 177 | } 178 | } -------------------------------------------------------------------------------- /Common/MicroServices.Common/Repository/IReadModelRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MicroServices.Common.Repository 5 | { 6 | public interface IReadModelRepository 7 | where T : ReadObject 8 | { 9 | IEnumerable GetAll(); 10 | T Get(Guid id); 11 | void Update(T t); 12 | void Insert(T t); 13 | } 14 | } -------------------------------------------------------------------------------- /Common/MicroServices.Common/Repository/IRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace MicroServices.Common.Repository 5 | { 6 | public interface IRepository 7 | { 8 | void Save(TAggregate aggregate) where TAggregate : Aggregate; 9 | TAggregate GetById(Guid id) where TAggregate : Aggregate; 10 | Task SaveAsync(TAggregate aggregate) where TAggregate : Aggregate; 11 | Task GetByIdAsync(Guid id) where TAggregate : Aggregate; 12 | } 13 | } -------------------------------------------------------------------------------- /Common/MicroServices.Common/Repository/InMemoryReadModelRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace MicroServices.Common.Repository 5 | { 6 | public class InMemoryReadModelRepository 7 | : IReadModelRepository where T : ReadObject 8 | { 9 | private readonly Dictionary items = new Dictionary(); 10 | public T Get(Guid id) 11 | { 12 | return items[id]; 13 | } 14 | 15 | public IEnumerable GetAll() 16 | { 17 | return items.Values; 18 | } 19 | 20 | public void Insert(T t) 21 | { 22 | items.Add(t.Id, t); 23 | } 24 | 25 | public void Update(T t) 26 | { 27 | items[t.Id] = t; 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Common/MicroServices.Common/Repository/InMemoryRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Newtonsoft.Json; 5 | using MicroServices.Common.MessageBus; 6 | using System.Threading.Tasks; 7 | using MicroServices.Common.Exceptions; 8 | 9 | namespace MicroServices.Common.Repository 10 | { 11 | public class InMemoryRepository : Repository 12 | { 13 | private readonly IMessageBus bus; 14 | public Dictionary> EventStore = new Dictionary>(); 15 | private readonly List latestEvents = new List(); 16 | private readonly JsonSerializerSettings serializationSettings; 17 | 18 | public InMemoryRepository(IMessageBus bus) 19 | { 20 | this.bus = bus; 21 | serializationSettings = new JsonSerializerSettings 22 | { 23 | TypeNameHandling = TypeNameHandling.All 24 | }; 25 | } 26 | 27 | public override Task SaveAsync(TAggregate aggregate) 28 | { 29 | throw new NotImplementedException(); 30 | } 31 | 32 | public override Task GetByIdAsync(Guid id) 33 | { 34 | throw new NotImplementedException(); 35 | } 36 | 37 | 38 | public override void Save(TAggregate aggregate) 39 | { 40 | var eventsToSave = aggregate.GetUncommittedEvents().ToList(); 41 | var serializedEvents = eventsToSave.Select(Serialize).ToList(); 42 | var expectedVersion = CalculateExpectedVersion(aggregate, eventsToSave); 43 | if (expectedVersion < 0) 44 | { 45 | EventStore.Add(aggregate.Id, serializedEvents); 46 | } 47 | else 48 | { 49 | var existingEvents = EventStore[aggregate.Id]; 50 | var currentversion = existingEvents.Count - 1; 51 | if (currentversion != expectedVersion) 52 | { 53 | throw new AggregateVersionException(aggregate.Id, typeof(TAggregate), currentversion, expectedVersion); 54 | } 55 | existingEvents.AddRange(serializedEvents); 56 | } 57 | latestEvents.AddRange(eventsToSave); 58 | if (bus != null) 59 | { 60 | foreach (var latestEvent in latestEvents) 61 | { 62 | bus.Publish(latestEvent); 63 | } 64 | } 65 | aggregate.MarkEventsAsCommitted(); 66 | } 67 | 68 | private string Serialize(Event arg) 69 | { 70 | return JsonConvert.SerializeObject(arg, serializationSettings); 71 | } 72 | 73 | public IEnumerable GetLatestEvents() 74 | { 75 | return latestEvents; 76 | } 77 | 78 | public override TAggregate GetById(Guid id) 79 | { 80 | if (EventStore.ContainsKey(id)) 81 | { 82 | var events = EventStore[id]; 83 | var deserializedEvents = events.Select(e => JsonConvert.DeserializeObject(e, serializationSettings) as Event); 84 | return BuildAggregate(deserializedEvents); 85 | } 86 | 87 | throw new AggregateNotFoundException(id, typeof(TAggregate)); 88 | } 89 | 90 | public void AddEvents(Dictionary> eventsForAggregates) 91 | { 92 | foreach (var eventsForAggregate in eventsForAggregates) 93 | { 94 | EventStore.Add(eventsForAggregate.Key, eventsForAggregate.Value.Select(Serialize).ToList()); 95 | } 96 | } 97 | } 98 | } -------------------------------------------------------------------------------- /Common/MicroServices.Common/Repository/RedisReadModelRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Newtonsoft.Json; 5 | using StackExchange.Redis; 6 | 7 | namespace MicroServices.Common.Repository 8 | { 9 | public class RedisReadModelRepository 10 | : IReadModelRepository where T : ReadObject 11 | { 12 | private readonly IDatabase database; 13 | 14 | public RedisReadModelRepository(IDatabase database) 15 | { 16 | this.database = database; 17 | } 18 | 19 | public IEnumerable GetAll() 20 | { 21 | var get = new RedisValue[] { InstanceName() + "*" }; 22 | var result = database.SortAsync(SetName(), sortType: SortType.Alphabetic, by: "nosort", get: get).Result; 23 | 24 | var readObjects = result.Select(v => JsonConvert.DeserializeObject(v)).AsEnumerable(); 25 | return readObjects; 26 | } 27 | 28 | public T Get(Guid id) 29 | { 30 | var key = Key(id); 31 | var result = database.StringGetAsync(key).Result; 32 | var dto = JsonConvert.DeserializeObject(result); 33 | return dto; 34 | } 35 | 36 | public void Update(T t) 37 | { 38 | var key = Key(t.Id); 39 | var serialised = JsonConvert.SerializeObject(t); 40 | database.StringSetAsync(key, serialised, when: When.Exists); 41 | } 42 | 43 | public void Insert(T t) 44 | { 45 | var serialised = JsonConvert.SerializeObject(t); 46 | var key = Key(t.Id); 47 | var transaction = database.CreateTransaction(); 48 | transaction.StringSetAsync(key, serialised); 49 | transaction.SetAddAsync(SetName(), t.Id.ToString("N")); 50 | var committed = transaction.ExecuteAsync().Result; 51 | if (!committed) 52 | { 53 | throw new ApplicationException("transaction failed. Now what?"); 54 | } 55 | } 56 | 57 | private string Key(Guid id) 58 | { 59 | return InstanceName() + id.ToString("N"); 60 | } 61 | 62 | private string InstanceName() 63 | { 64 | var type = typeof (T); 65 | return string.Format("{0}:", type.FullName); 66 | } 67 | private string SetName() 68 | { 69 | return string.Format("{0}Set", InstanceName()); 70 | } 71 | 72 | 73 | } 74 | } -------------------------------------------------------------------------------- /Common/MicroServices.Common/Repository/Repository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.Serialization; 4 | using System.Threading.Tasks; 5 | 6 | namespace MicroServices.Common.Repository 7 | { 8 | public abstract class Repository : IRepository 9 | { 10 | public abstract void Save(TAggregate aggregate) where TAggregate : Aggregate; 11 | public abstract TAggregate GetById(Guid id) where TAggregate : Aggregate; 12 | public abstract Task SaveAsync(TAggregate aggregate) where TAggregate : Aggregate; 13 | public abstract Task GetByIdAsync(Guid id) where TAggregate : Aggregate; 14 | 15 | protected int CalculateExpectedVersion(Aggregate aggregate, List events) 16 | { 17 | var expectedVersion = aggregate.Version - events.Count; 18 | return expectedVersion; 19 | } 20 | 21 | protected TAggregate BuildAggregate(IEnumerable events) where TAggregate : Aggregate 22 | { 23 | var result = (TAggregate)Activator.CreateInstance(typeof(TAggregate), true); 24 | result.LoadStateFromHistory(events); 25 | return result; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /Common/MicroServices.Common/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Invoke-NuGetPackageRestore.psm1: -------------------------------------------------------------------------------- 1 | #Requires -Version 2.0 2 | function Invoke-NuGetPackageRestore 3 | { 4 | [CmdletBinding(DefaultParameterSetName="Wait")] 5 | param 6 | ( 7 | [parameter(Position=0,Mandatory=$true,ValueFromPipeline=$true,HelpMessage="The path to the file to restore NuGet packages for (e.g. a .sln or .csproj file).")] 8 | [ValidateScript({Test-Path $_})] 9 | [string] $Path, 10 | 11 | [parameter(Mandatory=$false)] 12 | [Alias("Show","S")] 13 | [switch] $ShowNugetWindow 14 | ) 15 | 16 | BEGIN { } 17 | END { } 18 | PROCESS 19 | { 20 | Set-StrictMode -Version Latest 21 | 22 | # Default the ParameterSet variables that may not have been set depending on which parameter set is being used. This is required for PowerShell v2.0 compatibility. 23 | if (!(Test-Path Variable:Private:AutoLaunchBuildLogOnFailure)) { $AutoLaunchBuildLogOnFailure = $false } 24 | if (!(Test-Path Variable:Private:KeepBuildLogOnSuccessfulBuilds)) { $KeepBuildLogOnSuccessfulBuilds = $false } 25 | if (!(Test-Path Variable:Private:PassThru)) { $PassThru = $false } 26 | 27 | $buildCrashed = $false; 28 | $windowStyle = if ($ShowNugetWindow) { "Normal" } else { "Hidden" } 29 | 30 | $nugetExe = Get-NuGetExePath 31 | Write-Debug "NuGet.exe Path [$nugetExe]" 32 | 33 | if ($nugetExe -eq $null) 34 | { 35 | Write-Error "Could not find NuGet.exe" 36 | return $null 37 | } 38 | $argumentList = "restore " + $Path 39 | 40 | try 41 | { 42 | if ($PassThru) 43 | { 44 | $process = Start-Process $nugetExe.FullName -ArgumentList $argumentList -Wait -PassThru -WindowStyle $windowStyle 45 | } 46 | else 47 | { 48 | $process = Start-Process $nugetExe.FullName -ArgumentList $argumentList -Wait -PassThru -WindowStyle $windowStyle 49 | $processExitCode = $process.ExitCode 50 | } 51 | } 52 | catch 53 | { 54 | $errorMessage = $_ 55 | Write-Error ("Unexpect error occured while building ""$Path"": $errorMessage" ); 56 | } 57 | 58 | return $processExitCode -eq 0 59 | } 60 | } 61 | 62 | function Get-ChildItemToDepth { 63 | param( 64 | [String]$Path = $PWD, 65 | [String]$Filter = "*", 66 | [Byte]$ToDepth = 255, 67 | [Byte]$CurrentDepth = 0, 68 | [Switch]$DebugMode 69 | ) 70 | 71 | $CurrentDepth++ 72 | if ($DebugMode) { $DebugPreference = "Continue" } 73 | 74 | Get-ChildItem $Path | ForEach-Object { 75 | $_ | Where-Object { $_.Name -like $Filter } 76 | 77 | if ($_.PsIsContainer) { 78 | if ($CurrentDepth -le $ToDepth) { 79 | # Callback to this function 80 | Get-ChildItemToDepth -Path $_.FullName -Filter $Filter -ToDepth $ToDepth -CurrentDepth $CurrentDepth 81 | } else { 82 | Write-Debug $("Skipping GCI for Folder: $($_.FullName) " + 83 | "(Why: Current depth $CurrentDepth vs limit depth $ToDepth)") 84 | } 85 | } 86 | } 87 | } 88 | 89 | function Get-NuGetExePath 90 | { 91 | #$nugetExes = Get-ChildItem $PSScriptRoot NuGet.exe -Recurse 92 | $nugetExes = Get-ChildItemToDepth -Path $PSScriptRoot -Filter NuGet.exe -ToDepth 2 93 | 94 | if ($nugetExes -eq $null) { return $null } 95 | 96 | return $nugetExes[0] 97 | } 98 | 99 | Export-ModuleMember -Function Invoke-NuGetPackageRestore -------------------------------------------------------------------------------- /Products.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2009 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReadModelService", "ReadModelService", "{51119099-EDEC-4325-96EC-1BC7483E6BB6}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DomainService", "DomainService", "{F13AB59D-7BF7-460F-AF5E-97B65BFAE44C}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{3A37F4C0-8133-403A-9490-856E00D4A5C7}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Products.Common", "Products\Products.Common\Products.Common.csproj", "{5E571774-1D77-4F79-AF04-27A85DAC1340}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Products.ReadModels.Client", "Products\Products.ReadModels.Client\Products.ReadModels.Client.csproj", "{C46D3E71-E08D-4F45-B54B-03E3759028E3}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Products.ReadModels.Service", "Products\Products.ReadModels.Service\Products.ReadModels.Service.csproj", "{D91AEECC-CA6A-48A2-90D6-5146D945DEAB}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Products.Service", "Products\Products.Service\Products.Service.csproj", "{4EF4FA1A-483A-4F5C-9A98-012BE17B7672}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Products.Service.UnitTests", "Products\Products.Service.UnitTests\Products.Service.UnitTests.csproj", "{0C3E7514-3CA5-4FF5-BD42-F19EF1A4783D}" 21 | EndProject 22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicroServices.Common", "Common\MicroServices.Common\MicroServices.Common.csproj", "{D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}" 23 | EndProject 24 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicroServices.Common.General", "Common\MicroServices.Common.General\MicroServices.Common.General.csproj", "{FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}" 25 | EndProject 26 | Global 27 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 28 | Debug|Any CPU = Debug|Any CPU 29 | Release|Any CPU = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 32 | {5E571774-1D77-4F79-AF04-27A85DAC1340}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {5E571774-1D77-4F79-AF04-27A85DAC1340}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {5E571774-1D77-4F79-AF04-27A85DAC1340}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {5E571774-1D77-4F79-AF04-27A85DAC1340}.Release|Any CPU.Build.0 = Release|Any CPU 36 | {C46D3E71-E08D-4F45-B54B-03E3759028E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {C46D3E71-E08D-4F45-B54B-03E3759028E3}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {C46D3E71-E08D-4F45-B54B-03E3759028E3}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {C46D3E71-E08D-4F45-B54B-03E3759028E3}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {D91AEECC-CA6A-48A2-90D6-5146D945DEAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 41 | {D91AEECC-CA6A-48A2-90D6-5146D945DEAB}.Debug|Any CPU.Build.0 = Debug|Any CPU 42 | {D91AEECC-CA6A-48A2-90D6-5146D945DEAB}.Release|Any CPU.ActiveCfg = Release|Any CPU 43 | {D91AEECC-CA6A-48A2-90D6-5146D945DEAB}.Release|Any CPU.Build.0 = Release|Any CPU 44 | {4EF4FA1A-483A-4F5C-9A98-012BE17B7672}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 45 | {4EF4FA1A-483A-4F5C-9A98-012BE17B7672}.Debug|Any CPU.Build.0 = Debug|Any CPU 46 | {4EF4FA1A-483A-4F5C-9A98-012BE17B7672}.Release|Any CPU.ActiveCfg = Release|Any CPU 47 | {4EF4FA1A-483A-4F5C-9A98-012BE17B7672}.Release|Any CPU.Build.0 = Release|Any CPU 48 | {0C3E7514-3CA5-4FF5-BD42-F19EF1A4783D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {0C3E7514-3CA5-4FF5-BD42-F19EF1A4783D}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {0C3E7514-3CA5-4FF5-BD42-F19EF1A4783D}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {0C3E7514-3CA5-4FF5-BD42-F19EF1A4783D}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 53 | {D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}.Debug|Any CPU.Build.0 = Debug|Any CPU 54 | {D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}.Release|Any CPU.ActiveCfg = Release|Any CPU 55 | {D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}.Release|Any CPU.Build.0 = Release|Any CPU 56 | {FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 57 | {FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}.Debug|Any CPU.Build.0 = Debug|Any CPU 58 | {FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}.Release|Any CPU.ActiveCfg = Release|Any CPU 59 | {FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}.Release|Any CPU.Build.0 = Release|Any CPU 60 | EndGlobalSection 61 | GlobalSection(SolutionProperties) = preSolution 62 | HideSolutionNode = FALSE 63 | EndGlobalSection 64 | GlobalSection(NestedProjects) = preSolution 65 | {5E571774-1D77-4F79-AF04-27A85DAC1340} = {3A37F4C0-8133-403A-9490-856E00D4A5C7} 66 | {C46D3E71-E08D-4F45-B54B-03E3759028E3} = {51119099-EDEC-4325-96EC-1BC7483E6BB6} 67 | {D91AEECC-CA6A-48A2-90D6-5146D945DEAB} = {51119099-EDEC-4325-96EC-1BC7483E6BB6} 68 | {4EF4FA1A-483A-4F5C-9A98-012BE17B7672} = {F13AB59D-7BF7-460F-AF5E-97B65BFAE44C} 69 | {0C3E7514-3CA5-4FF5-BD42-F19EF1A4783D} = {F13AB59D-7BF7-460F-AF5E-97B65BFAE44C} 70 | EndGlobalSection 71 | GlobalSection(ExtensibilityGlobals) = postSolution 72 | SolutionGuid = {1A5A6B15-5E29-4632-B93B-D86387D67D28} 73 | EndGlobalSection 74 | EndGlobal 75 | -------------------------------------------------------------------------------- /Products/Products.Common/Dto/ProductDto.cs: -------------------------------------------------------------------------------- 1 | using MicroServices.Common; 2 | 3 | namespace Products.Common.Dto 4 | { 5 | public class ProductDto : ReadObject 6 | { 7 | public string Name { get; set; } 8 | public string Description { get; set; } 9 | public decimal Price { get; set; } 10 | public int Version { get; set; } 11 | public string DisplayName { get; set; } 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /Products/Products.Common/Events/ProductEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using MicroServices.Common; 4 | 5 | namespace Products.Common.Events 6 | { 7 | public class ProductCreated : Event 8 | { 9 | public string Name { get; private set; } 10 | public string Description { get; private set; } 11 | public decimal Price { get; private set; } 12 | public ProductCreated(Guid id, string name, string description, decimal price) 13 | { 14 | Id = id; 15 | Name = name; 16 | Description = description; 17 | Price = price; 18 | } 19 | [JsonConstructor] 20 | private ProductCreated(Guid id, string name, string description, decimal price, int version) 21 | : this(id, name, description, price) 22 | { 23 | Version = version; 24 | } 25 | } 26 | 27 | public class ProductNameChanged : Event 28 | { 29 | public ProductNameChanged(Guid id, string newName) 30 | { 31 | Id = id; 32 | NewName = newName; 33 | } 34 | 35 | [JsonConstructor] 36 | private ProductNameChanged(Guid id, string newName, int version) : this(id, newName) 37 | { 38 | Version = version; 39 | } 40 | 41 | public string NewName { get; private set; } 42 | } 43 | 44 | public class ProductDescriptionChanged : Event 45 | { 46 | public ProductDescriptionChanged(Guid id, string newDescription) 47 | { 48 | Id = id; 49 | NewDescription = newDescription; 50 | } 51 | 52 | [JsonConstructor] 53 | private ProductDescriptionChanged(Guid id, string newDescription, int version) : this(id, newDescription) 54 | { 55 | Version = version; 56 | } 57 | 58 | public string NewDescription { get; private set; } 59 | } 60 | 61 | public class ProductPriceChanged : Event 62 | { 63 | public ProductPriceChanged(Guid id, decimal newPrice) 64 | { 65 | Id = id; 66 | NewPrice = newPrice; 67 | } 68 | 69 | [JsonConstructor] 70 | private ProductPriceChanged(Guid id, decimal newPrice, int version) 71 | : this(id, newPrice) 72 | { 73 | Version = version; 74 | } 75 | 76 | public decimal NewPrice { get; private set; } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Products/Products.Common/Products.Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {5E571774-1D77-4F79-AF04-27A85DAC1340} 8 | Library 9 | Properties 10 | Products.Common 11 | Products.Common 12 | v4.6.2 13 | 512 14 | 15 | ..\ 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\NewId.2.1.3\lib\net45\NewId.dll 37 | True 38 | 39 | 40 | ..\packages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll 41 | True 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | {fc24d38f-fb96-43e1-b21e-b6e1bfa1dc11} 63 | MicroServices.Common.General 64 | 65 | 66 | 67 | 74 | -------------------------------------------------------------------------------- /Products/Products.Common/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("Products.Common")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Products.Common")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 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("5e571774-1d77-4f79-af04-27a85dac1340")] 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 | -------------------------------------------------------------------------------- /Products/Products.Common/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Products/Products.ReadModels.Client/IProductsView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Products.Common.Dto; 4 | 5 | namespace Products.ReadModels.Client 6 | { 7 | public interface IProductsView 8 | { 9 | ProductDto GetById(Guid id); 10 | IEnumerable GetProducts(); 11 | void Initialize(); 12 | void Reset(); 13 | void UpdateLocalCache(ProductDto newValue); 14 | } 15 | } -------------------------------------------------------------------------------- /Products/Products.ReadModels.Client/Products.ReadModels.Client.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {C46D3E71-E08D-4F45-B54B-03E3759028E3} 8 | Library 9 | Properties 10 | Products.ReadModels.Client 11 | Products.ReadModels.Client 12 | v4.6.2 13 | 512 14 | 15 | 16 | 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | pdbonly 27 | true 28 | bin\Release\ 29 | TRACE 30 | prompt 31 | 4 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | {fc24d38f-fb96-43e1-b21e-b6e1bfa1dc11} 51 | MicroServices.Common.General 52 | 53 | 54 | {d3d97918-2c26-4bae-a0c5-f90b6f1085dd} 55 | MicroServices.Common 56 | 57 | 58 | {5E571774-1D77-4F79-AF04-27A85DAC1340} 59 | Products.Common 60 | 61 | 62 | 63 | 70 | -------------------------------------------------------------------------------- /Products/Products.ReadModels.Client/ProductsView.cs: -------------------------------------------------------------------------------- 1 | using Products.Common.Dto; 2 | using MicroServices.Common.General.Util; 3 | using MicroServices.Common.MessageBus; 4 | using System; 5 | using System.Collections.Concurrent; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Reflection; 9 | using System.Text; 10 | using System.Threading; 11 | using System.Threading.Tasks; 12 | 13 | namespace Products.ReadModels.Client 14 | { 15 | public class ProductsView : IProductsView 16 | { 17 | private const string ProductsServiceUrl = "http://localhost:8181/api/"; 18 | 19 | private static ConcurrentDictionary products = new ConcurrentDictionary(); 20 | 21 | static ProductsView() 22 | { 23 | InitializeProducts(); 24 | 25 | var ProductsEventListener = new TransientSubscriber( 26 | "Products_client_productview_" + Assembly.GetEntryAssembly().FullName.Split(',').FirstOrDefault(), 27 | "Products.Common.Events", 28 | () => 29 | { 30 | ResetProducts(); 31 | InitializeProducts(); 32 | }); 33 | } 34 | 35 | public void Initialize() 36 | { 37 | InitializeProducts(); 38 | } 39 | 40 | public IEnumerable GetProducts() 41 | { 42 | return products.Values.AsEnumerable(); 43 | } 44 | 45 | public ProductDto GetById(Guid id) 46 | { 47 | ProductDto result; 48 | if (products.TryGetValue(id, out result)) 49 | { 50 | return result; 51 | } 52 | else 53 | { 54 | throw new ApplicationException("Cannot find the product."); 55 | } 56 | } 57 | 58 | public void UpdateLocalCache(ProductDto newValue) 59 | { 60 | ProductDto retrievedValue; 61 | Guid searchKey = newValue.Id; 62 | 63 | if (products.TryGetValue(searchKey, out retrievedValue)) 64 | { 65 | if (!products.TryUpdate(searchKey, newValue, retrievedValue)) 66 | { 67 | throw new ApplicationException("Failed to update the product in the cache"); 68 | } 69 | } 70 | else 71 | { 72 | if (!products.TryAdd(searchKey, newValue)) 73 | { 74 | throw new ApplicationException("Failed to add new product to the cache"); 75 | } 76 | } 77 | } 78 | 79 | public void Reset() 80 | { 81 | ResetProducts(); 82 | } 83 | 84 | private static void InitializeProducts() 85 | { 86 | var result = Try.To(LoadProducts) 87 | .OnFailedAttempt(() => Thread.Sleep(1000)) 88 | .UpTo(3) 89 | .Times(); 90 | 91 | if (!result.Succeeded) 92 | throw new ApplicationException("Failed to load the Products from the Products service.", result.Exception); 93 | } 94 | 95 | private static void LoadProducts() 96 | { 97 | var apiClient = new ApiClient(); 98 | var apiResponse = apiClient.LoadMany(ProductsServiceUrl + "products"); 99 | 100 | foreach (var product in apiResponse) 101 | products.TryAdd(product.Id, product); 102 | } 103 | 104 | private static void ResetProducts() 105 | { 106 | products = new ConcurrentDictionary(); 107 | } 108 | 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /Products/Products.ReadModels.Client/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("Products.ReadModels.Client")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Products.ReadModels.Client")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 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("c46d3e71-e08d-4f45-b54b-03e3759028e3")] 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 | -------------------------------------------------------------------------------- /Products/Products.ReadModels.Service/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Products/Products.ReadModels.Service/Controllers/ProductsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Web.Http; 3 | using MicroServices.Common.Exceptions; 4 | 5 | namespace Products.ReadModels.Service.Controllers 6 | { 7 | public class ProductsController : ApiController 8 | { 9 | [HttpGet] 10 | public IHttpActionResult Get(Guid id) 11 | { 12 | var view = ServiceLocator.ProductView; 13 | try 14 | { 15 | var dto = view.GetById(id); 16 | return Ok(dto); 17 | } 18 | catch (ReadModelNotFoundException) 19 | { 20 | return NotFound(); 21 | } 22 | } 23 | 24 | [HttpGet] 25 | public IHttpActionResult Get() 26 | { 27 | var view = ServiceLocator.ProductView; 28 | var result = view.GetAll(); 29 | return Ok(result); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Products/Products.ReadModels.Service/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Owin.Hosting; 3 | using Topshelf; 4 | 5 | namespace Products.ReadModels.Service 6 | { 7 | class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | HostFactory.Run(x => 12 | { 13 | x.Service(s => 14 | { 15 | s.ConstructUsing(name => new ProductsReadModelService()); 16 | s.WhenStarted(tc => tc.Start()); 17 | s.WhenStopped(tc => tc.Stop()); 18 | }); 19 | x.RunAsLocalSystem(); 20 | 21 | x.SetDescription("Products - Read Models Service"); 22 | x.SetDisplayName("Products - Read Models Service"); 23 | x.SetServiceName("Products.ReadModels.Service"); 24 | }); 25 | } 26 | } 27 | public class ProductsReadModelService 28 | { 29 | private IDisposable webApp; 30 | public void Start() 31 | { 32 | const string baseUri = "http://localhost:8181"; 33 | Console.WriteLine("Starting Products Read Model Service..."); 34 | webApp = WebApp.Start(baseUri); 35 | Console.WriteLine("Server running at {0} - press Enter to quit. ", baseUri); 36 | } 37 | 38 | public void Stop() 39 | { 40 | webApp.Dispose(); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Products/Products.ReadModels.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("Products.ReadModels.Service")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Products.ReadModels.Service")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 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("d91aeecc-ca6a-48a2-90d6-5146d945deab")] 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 | -------------------------------------------------------------------------------- /Products/Products.ReadModels.Service/ServiceLocator.cs: -------------------------------------------------------------------------------- 1 | using Products.ReadModels.Service.Views; 2 | using MicroServices.Common.MessageBus; 3 | 4 | namespace Products.ReadModels.Service 5 | { 6 | public static class ServiceLocator 7 | { 8 | public static IMessageBus Bus { get; set; } 9 | public static ProductView ProductView { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /Products/Products.ReadModels.Service/Startup.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Web.Http; 3 | using Owin; 4 | using MicroServices.Common.MessageBus; 5 | using MicroServices.Common.Repository; 6 | using Products.ReadModels.Service.Views; 7 | using MicroServices.Common.Exceptions; 8 | using EasyNetQ; 9 | using MicroServices.Common; 10 | using System; 11 | using Newtonsoft.Json; 12 | using System.Collections.Generic; 13 | using Products.Common.Dto; 14 | using Products.Common.Events; 15 | using StackExchange.Redis; 16 | using Aggregate = MicroServices.Common.Aggregate; 17 | using MicroServices.Common.General.Util; 18 | 19 | namespace Products.ReadModels.Service 20 | { 21 | internal class Startup 22 | { 23 | public void Configuration(IAppBuilder app) 24 | { 25 | var webApiConfiguration = ConfigureWebApi(); 26 | webApiConfiguration.EnsureInitialized(); 27 | app.UseWebApi(webApiConfiguration); 28 | ConfigureHandlers(); 29 | } 30 | 31 | private static HttpConfiguration ConfigureWebApi() 32 | { 33 | var config = new HttpConfiguration(); 34 | config.MapHttpAttributeRoutes(); 35 | config.Routes.MapHttpRoute( 36 | "DefaultApi", 37 | "api/{controller}/{id}", 38 | new { id = RouteParameter.Optional }); 39 | return config; 40 | } 41 | 42 | private void ConfigureHandlers() 43 | { 44 | var redis = ConnectionMultiplexer.Connect("localhost"); 45 | var productView = new ProductView(new RedisReadModelRepository(redis.GetDatabase())); 46 | ServiceLocator.ProductView = productView; 47 | 48 | var eventMappings = new EventHandlerDiscovery() 49 | .Scan(productView) 50 | .Handlers; 51 | 52 | var subscriptionName = "Products_readmodel"; 53 | var topicFilter1 = "Products.Common.Events"; 54 | 55 | var b = RabbitHutch.CreateBus("host=localhost"); 56 | 57 | b.Subscribe(subscriptionName, 58 | m => 59 | { 60 | Aggregate handler; 61 | var messageType = Type.GetType(m.MessageTypeName); 62 | var handlerFound = eventMappings.TryGetValue(messageType, out handler); 63 | if (handlerFound) 64 | { 65 | var @event = JsonConvert.DeserializeObject(m.SerialisedMessage, messageType); 66 | handler.AsDynamic().ApplyEvent(@event, ((Event)@event).Version); 67 | } 68 | }, 69 | q => q.WithTopic(topicFilter1)); 70 | 71 | var bus = new RabbitMqBus(b); 72 | 73 | ServiceLocator.Bus = bus; 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /Products/Products.ReadModels.Service/Views/ProductView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Products.Common.Dto; 4 | using MicroServices.Common; 5 | using Products.Common.Events; 6 | using MicroServices.Common.Exceptions; 7 | using MicroServices.Common.Repository; 8 | 9 | namespace Products.ReadModels.Service.Views 10 | { 11 | public class ProductView : ReadModelAggregate, 12 | IHandle, 13 | IHandle, 14 | IHandle, 15 | IHandle 16 | { 17 | private const string displayFormat = "{1} ({0})"; 18 | private readonly IReadModelRepository repository; 19 | 20 | public ProductView(IReadModelRepository repository) 21 | { 22 | this.repository = repository; 23 | } 24 | 25 | public ProductDto GetById(Guid id) 26 | { 27 | try 28 | { 29 | return repository.Get(id); 30 | } 31 | catch 32 | { 33 | throw new ReadModelNotFoundException(id, typeof(ProductDto)); 34 | } 35 | } 36 | 37 | public IEnumerable GetAll() 38 | { 39 | return repository.GetAll(); 40 | } 41 | 42 | public void Apply(ProductCreated e) 43 | { 44 | var dto = new ProductDto 45 | { 46 | Id = e.Id, 47 | Name = e.Name, 48 | Description = e.Description, 49 | Price = e.Price, 50 | Version = e.Version, 51 | DisplayName = string.Format(displayFormat, e.Name, e.Description), 52 | }; 53 | repository.Insert(dto); 54 | } 55 | public void Apply(ProductNameChanged e) 56 | { 57 | var product = GetById(e.Id); 58 | product.Name = e.NewName; 59 | product.Version = e.Version; 60 | product.DisplayName = string.Format(displayFormat, product.Name, product.Description); 61 | repository.Update(product); 62 | } 63 | public void Apply(ProductDescriptionChanged e) 64 | { 65 | var product = GetById(e.Id); 66 | product.Description = e.NewDescription; 67 | product.Version = e.Version; 68 | product.DisplayName = string.Format(displayFormat, product.Name, product.Description); 69 | repository.Update(product); 70 | } 71 | public void Apply(ProductPriceChanged e) 72 | { 73 | var product = GetById(e.Id); 74 | product.Price = e.NewPrice; 75 | product.Version = e.Version; 76 | repository.Update(product); 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /Products/Products.ReadModels.Service/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Products/Products.Service.UnitTests/ProductEvents.cs: -------------------------------------------------------------------------------- 1 | using MicroServices.Common; 2 | using Products.Common.Events; 3 | using Products.Service.MicroServices.Products.Domain; 4 | using System; 5 | using System.Linq; 6 | using Xunit; 7 | 8 | namespace Products.Service.UnitTests 9 | { 10 | public class ProductEvents 11 | { 12 | [Fact] 13 | public void Should_generate_productcreated_event_when_creating_a_new_product() 14 | { 15 | var p = new Product(Guid.NewGuid(), "name", "description", 1.2m); 16 | var events = p.GetUncommittedEvents(); 17 | 18 | Assert.NotEmpty(events); 19 | Assert.Equal(1, events.Count()); 20 | Assert.IsType(events.First()); 21 | } 22 | 23 | [Fact] 24 | public void Should_change_name_when_a_namechanged_event_is_applied() 25 | { 26 | var p = new Product(Guid.NewGuid(), "name", "description", 1.2m); 27 | var e = new ProductNameChanged(p.Id, "new Name"); 28 | p.AsDynamic().Apply(e); 29 | 30 | Assert.Equal("new Name", p.Name); 31 | } 32 | 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Products/Products.Service.UnitTests/Products.Service.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {0C3E7514-3CA5-4FF5-BD42-F19EF1A4783D} 9 | Library 10 | Properties 11 | Products.Service.UnitTests 12 | Products.Service.UnitTests 13 | v4.6.2 14 | 512 15 | 16 | 17 | 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | 28 | 29 | pdbonly 30 | true 31 | bin\Release\ 32 | TRACE 33 | prompt 34 | 4 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll 47 | True 48 | 49 | 50 | ..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll 51 | True 52 | 53 | 54 | ..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll 55 | True 56 | 57 | 58 | ..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll 59 | True 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | {FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11} 73 | MicroServices.Common.General 74 | 75 | 76 | {5e571774-1d77-4f79-af04-27a85dac1340} 77 | Products.Common 78 | 79 | 80 | {4ef4fa1a-483a-4f5c-9a98-012be17b7672} 81 | Products.Service 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 91 | 92 | 93 | 94 | 101 | -------------------------------------------------------------------------------- /Products/Products.Service.UnitTests/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("Products.Service.UnitTests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Products.Service.UnitTests")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 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("0c3e7514-3ca5-4ff5-bd42-f19ef1a4783d")] 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 | -------------------------------------------------------------------------------- /Products/Products.Service.UnitTests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /Products/Products.Service.UnitTests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Products/Products.Service/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /Products/Products.Service/Controllers/ProductsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Web.Http; 3 | using System.Net; 4 | using System.Net.Http; 5 | using Products.Service.DataTransferObjects.Commands; 6 | using Products.Service.MicroServices.Products.Commands; 7 | using Products.Service.MicroServices.Products.Handlers; 8 | using MicroServices.Common.Exceptions; 9 | 10 | namespace Products.Service.Controllers 11 | { 12 | public class ProductsController : ApiController 13 | { 14 | private readonly ProductCommandHandlers handler; 15 | 16 | public ProductsController() 17 | : this(ServiceLocator.ProductCommands) 18 | {} 19 | 20 | public ProductsController(ProductCommandHandlers handler) 21 | { 22 | this.handler = handler; 23 | } 24 | 25 | [HttpPost] 26 | public IHttpActionResult Post(CreateProductCommand cmd) 27 | { 28 | if (string.IsNullOrWhiteSpace(cmd.Name)) 29 | { 30 | var response = new HttpResponseMessage(HttpStatusCode.Forbidden) 31 | { 32 | Content = new StringContent("code must be supplied in the body"), 33 | ReasonPhrase = "Missing product code" 34 | }; 35 | throw new HttpResponseException(response); 36 | } 37 | 38 | try 39 | { 40 | var command = new CreateProduct(Guid.NewGuid(), cmd.Name, cmd.Description, cmd.Price); 41 | handler.Handle(command); 42 | 43 | var link = new Uri(string.Format("http://localhost:8181/api/products/{0}", command.Id)); 44 | return Created(link, command); 45 | } 46 | catch (AggregateNotFoundException) 47 | { 48 | return NotFound(); 49 | } 50 | catch (AggregateDeletedException) 51 | { 52 | return Conflict(); 53 | } 54 | } 55 | 56 | [HttpPut] 57 | [Route("api/products/{id:guid}")] 58 | public IHttpActionResult Put(Guid id, AlterProductCommand cmd) 59 | { 60 | if (string.IsNullOrWhiteSpace(cmd.Name)) 61 | { 62 | var response = new HttpResponseMessage(HttpStatusCode.Forbidden) 63 | { 64 | Content = new StringContent("code must be supplied in the body"), 65 | ReasonPhrase = "Missing product code" 66 | }; 67 | throw new HttpResponseException(response); 68 | } 69 | 70 | try 71 | { 72 | var command = new AlterProduct(id, cmd.Version, cmd.Name, cmd.Description, cmd.Price); 73 | handler.Handle(command); 74 | 75 | return Ok(command); 76 | } 77 | catch (AggregateNotFoundException) 78 | { 79 | return NotFound(); 80 | } 81 | catch (AggregateDeletedException) 82 | { 83 | return Conflict(); 84 | } 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /Products/Products.Service/DataTransferObjects/Commands/Products/AlterProductCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Products.Service.DataTransferObjects.Commands 2 | { 3 | public class AlterProductCommand 4 | { 5 | public string Name { get; set; } 6 | public string Description { get; set; } 7 | public decimal Price { get; set; } 8 | public int Version { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Products/Products.Service/DataTransferObjects/Commands/Products/CreateProductCommand.cs: -------------------------------------------------------------------------------- 1 | namespace Products.Service.DataTransferObjects.Commands 2 | { 3 | public class CreateProductCommand 4 | { 5 | public string Name { get; set; } 6 | public string Description { get; set; } 7 | public decimal Price { get; set; } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Products/Products.Service/MicroServices/Products/Commands/ProductCommands.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MicroServices.Common; 3 | 4 | namespace Products.Service.MicroServices.Products.Commands 5 | { 6 | public class CreateProduct : ICommand 7 | { 8 | public Guid Id { get; private set; } 9 | public string Name { get; private set; } 10 | public string Description { get; private set; } 11 | public decimal Price { get; set; } 12 | 13 | public CreateProduct(Guid id, string name, string description, decimal price) 14 | { 15 | Id = id; 16 | Name = name; 17 | Description = description; 18 | Price = price; 19 | } 20 | } 21 | 22 | public class AlterProduct : ICommand 23 | { 24 | public Guid Id { get; private set; } 25 | public string NewTitle { get; set; } 26 | public string NewDescription { get; set; } 27 | public decimal NewPrice { get; set; } 28 | public int OriginalVersion { get; private set; } 29 | 30 | public AlterProduct(Guid id, int originalVersion, string newTitle, string newDescription, decimal newPrice) 31 | { 32 | Id = id; 33 | NewTitle = newTitle; 34 | NewDescription = newDescription; 35 | NewPrice = newPrice; 36 | OriginalVersion = originalVersion; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Products/Products.Service/MicroServices/Products/Domain/Product.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Products.Common.Events; 3 | using MicroServices.Common; 4 | 5 | namespace Products.Service.MicroServices.Products.Domain 6 | { 7 | public class Product : Aggregate 8 | { 9 | private Product() { } 10 | 11 | public Product(Guid id, string name, string description, decimal price) 12 | { 13 | ValidateName(name); 14 | 15 | ApplyEvent(new ProductCreated(id, name, description, price)); 16 | } 17 | 18 | public string Name { get; private set; } 19 | public string Description { get; private set; } 20 | public decimal Price { get; set; } 21 | 22 | private void Apply(ProductCreated e) 23 | { 24 | Id = e.Id; 25 | Name = e.Name; 26 | Description = e.Description; 27 | Price = e.Price; 28 | } 29 | 30 | private void Apply(ProductPriceChanged e) 31 | { 32 | Price = e.NewPrice; 33 | } 34 | 35 | private void Apply(ProductDescriptionChanged e) 36 | { 37 | Description = e.NewDescription; 38 | } 39 | 40 | private void Apply(ProductNameChanged e) 41 | { 42 | Name = e.NewName; 43 | } 44 | 45 | public void ChangeName(string newName, int originalVersion) 46 | { 47 | ValidateName(newName); 48 | ValidateVersion(originalVersion); 49 | 50 | ApplyEvent(new ProductNameChanged(Id, newName)); 51 | } 52 | 53 | public void ChangeDescription(string newDescription, int originalVersion) 54 | { 55 | ValidateVersion(originalVersion); 56 | 57 | ApplyEvent(new ProductDescriptionChanged(Id, newDescription)); 58 | } 59 | 60 | public void ChangePrice(decimal newPrice, int originalVersion) 61 | { 62 | ValidateVersion(originalVersion); 63 | 64 | ApplyEvent(new ProductPriceChanged(Id, newPrice)); 65 | } 66 | 67 | void ValidateName(string name) 68 | { 69 | if (string.IsNullOrWhiteSpace(name)) 70 | { 71 | throw new ArgumentException("Invalid name specified: cannot be empty.", "name"); 72 | } 73 | } 74 | 75 | void ValidateVersion(int version) 76 | { 77 | if (Version != version) 78 | { 79 | throw new ArgumentOutOfRangeException("version", "Invalid version specified: the version is out of sync."); 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Products/Products.Service/MicroServices/Products/Handlers/ProductCommandHandlers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Products.Common.Dto; 3 | using Products.Service.MicroServices.Products.Commands; 4 | using MicroServices.Common.Repository; 5 | 6 | namespace Products.Service.MicroServices.Products.Handlers 7 | { 8 | public class ProductCommandHandlers 9 | { 10 | private readonly IRepository repository; 11 | 12 | public ProductCommandHandlers(IRepository repository) 13 | { 14 | this.repository = repository; 15 | } 16 | 17 | public void Handle(CreateProduct message) 18 | { 19 | // Validation 20 | ProductDto existingProduct = null; 21 | 22 | // Process 23 | var series = new Products.Domain.Product(message.Id, message.Name, message.Description, message.Price); 24 | repository.Save(series); 25 | } 26 | 27 | public void Handle(AlterProduct message) 28 | { 29 | var product = repository.GetById(message.Id); 30 | 31 | int committedVersion = message.OriginalVersion; 32 | 33 | if (!String.Equals(product.Name, message.NewTitle, StringComparison.OrdinalIgnoreCase)) 34 | { 35 | product.ChangeName(message.NewTitle, committedVersion++); 36 | } 37 | 38 | if (!String.Equals(product.Description, message.NewDescription, StringComparison.OrdinalIgnoreCase)) 39 | { 40 | product.ChangeDescription(message.NewDescription, committedVersion++); 41 | } 42 | 43 | if (message.NewPrice != product.Price) 44 | { 45 | product.ChangePrice(message.NewPrice, committedVersion); 46 | } 47 | 48 | repository.Save(product); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /Products/Products.Service/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Owin.Hosting; 3 | using Topshelf; 4 | 5 | namespace Products.Service 6 | { 7 | public class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | HostFactory.Run(x => 12 | { 13 | x.Service(s => 14 | { 15 | s.ConstructUsing(name => new ProductsService()); 16 | s.WhenStarted(tc => tc.Start()); 17 | s.WhenStopped(tc => tc.Stop()); 18 | }); 19 | x.RunAsLocalSystem(); 20 | 21 | x.SetDescription("Products - Domain Service"); 22 | x.SetDisplayName("Products - Domain Service"); 23 | x.SetServiceName("Products.Service"); 24 | }); 25 | } 26 | } 27 | 28 | public class ProductsService 29 | { 30 | private IDisposable webApp; 31 | public void Start() 32 | { 33 | const string baseUri = "http://localhost:8180"; 34 | Console.WriteLine("Starting web Server..."); 35 | webApp = WebApp.Start(baseUri); 36 | Console.WriteLine("Server running at {0} - press Enter to quit. ", baseUri); 37 | } 38 | 39 | public void Stop() 40 | { 41 | webApp.Dispose(); 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Products/Products.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("Products.Service")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Products.Service")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 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 | // Version information for an assembly consists of the following four values: 23 | // 24 | // Major Version 25 | // Minor Version 26 | // Build Number 27 | // Revision 28 | // 29 | // You can specify all the values or you can default the Build and Revision Numbers 30 | // by using the '*' as shown below: 31 | // [assembly: AssemblyVersion("1.0.*")] 32 | [assembly: AssemblyVersion("1.0.0.0")] 33 | [assembly: AssemblyFileVersion("1.0.0.0")] 34 | -------------------------------------------------------------------------------- /Products/Products.Service/ServiceLocator.cs: -------------------------------------------------------------------------------- 1 | using Products.Service.MicroServices.Products.Handlers; 2 | using MicroServices.Common.MessageBus; 3 | 4 | namespace Products.Service 5 | { 6 | public static class ServiceLocator 7 | { 8 | public static IMessageBus Bus { get; set; } 9 | public static ProductCommandHandlers ProductCommands { get; set; } 10 | } 11 | } -------------------------------------------------------------------------------- /Products/Products.Service/Startup.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Web.Http; 3 | using EventStore.ClientAPI; 4 | using Owin; 5 | using Products.Service.MicroServices.Products.Handlers; 6 | using MicroServices.Common.MessageBus; 7 | using MicroServices.Common.Repository; 8 | using EasyNetQ; 9 | 10 | namespace Products.Service 11 | { 12 | internal class Startup 13 | { 14 | public void Configuration(IAppBuilder app) 15 | { 16 | var webApiConfiguration = ConfigureWebApi(); 17 | app.UseWebApi(webApiConfiguration); 18 | ConfigureHandlers(); 19 | } 20 | 21 | private static HttpConfiguration ConfigureWebApi() 22 | { 23 | var config = new HttpConfiguration(); 24 | 25 | // Enable Web API Attribute Routing 26 | config.MapHttpAttributeRoutes(); 27 | 28 | config.Routes.MapHttpRoute( 29 | "DefaultApi", 30 | "api/{controller}/{id}", 31 | new { id = RouteParameter.Optional }); 32 | return config; 33 | } 34 | 35 | private void ConfigureHandlers() 36 | { 37 | var bus = new RabbitMqBus(RabbitHutch.CreateBus("host=localhost")); 38 | ServiceLocator.Bus = bus; 39 | 40 | //Should get this from a config setting instead of hardcoding it. 41 | var eventStoreConnection = EventStoreConnection.Create(new IPEndPoint(IPAddress.Loopback, 12900)); 42 | eventStoreConnection.ConnectAsync().Wait(); 43 | var repository = new EventStoreRepository(eventStoreConnection, bus); 44 | 45 | ServiceLocator.ProductCommands = new ProductCommandHandlers(repository); 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /Products/Products.Service/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ExtHttpPort: 2800 3 | IntHttpPort: 2801 4 | ClusterGossipPort: 38700 5 | ExtTcpPort: 3900 6 | IntTcpPort: 3901 7 | Db: ..\Barista\Data 8 | Log: ..\Barista\Logs 9 | DiscoverViaDns: false 10 | ClusterSize: 1 11 | --- -------------------------------------------------------------------------------- /Products/Products.Service/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # microservices-prototype 2 | 3 | https://koukia.ca/a-microservices-implementation-journey-part-1-9f6471fe917 4 | 5 | -------------------------------------------------------------------------------- /Reset-Data.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | $Clear = "all", 3 | $EventStoreServerLocation = "c:\eventstore\server", 4 | $RedisInstallLocation = "C:\Program Files\Redis" 5 | ) 6 | 7 | If("all","eventstore","redis" -NotContains $Clear) 8 | { 9 | Throw "$($Clear) is not a valid value. Please use eventstore, redis or all" 10 | } 11 | 12 | $eventStores = @("MicroCafe") 13 | 14 | If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) 15 | { 16 | $arguments = "-file """ + $myinvocation.mycommand.definition + """ -EventStoreServerLocation """ + $EventStoreServerLocation + """" 17 | Start-Process "$psHome\powershell.exe" -Verb runAs -ArgumentList $arguments 18 | break 19 | } 20 | 21 | If(!($Clear -eq "redis")) 22 | { 23 | Write-Host "Cleaning event store data" 24 | $eventStoreRoot = (Get-Item $EventStoreServerLocation).Parent.FullName 25 | 26 | #Kill existing Event Store processes 27 | $eventStoreProcesses = get-process | ?{$_.name -eq "EventStore.ClusterNode"} 28 | $eventStoreProcesses | foreach { stop-process -id $_.Id } 29 | 30 | #Delete data folders (adjust for your own environment as needed) 31 | $eventStoreDataDirectories = $eventStores | foreach { Join-Path $eventStoreRoot $_ } | foreach { Join-Path $_ "Data" } 32 | $eventStoreDataDirectories | foreach { rmdir $_ -recurse } 33 | } 34 | 35 | If(!($Clear -eq "eventstore")) 36 | { 37 | Write-Host "Cleaning redis data" 38 | $redisCli = Join-Path $RedisInstallLocation "redis-cli.exe" 39 | $redisArgs = "flushdb" 40 | Start-Process $redisCli -ArgumentList $redisArgs 41 | } -------------------------------------------------------------------------------- /Sales.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27004.2009 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Common", "Common", "{996FE8C5-4EBF-4CF9-9A6C-7A21F23A4B94}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReadModelService", "ReadModelService", "{BB388DD9-BEE8-4755-952E-0F51CA60AAC1}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DomainService", "DomainService", "{E4FA608F-F4B2-41AA-B819-3BAC53839407}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sales.Common", "Sales\Sales.Common\Sales.Common.csproj", "{D3478255-CB14-4F5D-AC78-2E48B5EC2439}" 13 | EndProject 14 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sales.ReadModels.Service", "Sales\Sales.ReadModels.Service\Sales.ReadModels.Service.csproj", "{B1ECFB12-A337-49FF-AD09-304B5292A548}" 15 | EndProject 16 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sales.Service", "Sales\Sales.Service\Sales.Service.csproj", "{2D8DF944-BAC5-4CE1-807A-6FFCE3C51284}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sales.Service.Tests", "Sales\Sales.Service.Tests\Sales.Service.Tests.csproj", "{FDED84A3-EC77-42E1-BDAA-E5A2A56E800F}" 19 | EndProject 20 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicroServices.Common.General", "Common\MicroServices.Common.General\MicroServices.Common.General.csproj", "{FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}" 21 | EndProject 22 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MicroServices.Common", "Common\MicroServices.Common\MicroServices.Common.csproj", "{D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}" 23 | EndProject 24 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Integrations", "Integrations", "{1F0F6FA5-1887-48E5-9E89-A195CAF8429A}" 25 | EndProject 26 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Products", "Products", "{A7C685A0-C705-475B-9A28-059C842A5224}" 27 | EndProject 28 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Products.ReadModels.Client", "Products\Products.ReadModels.Client\Products.ReadModels.Client.csproj", "{C46D3E71-E08D-4F45-B54B-03E3759028E3}" 29 | EndProject 30 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Products.Common", "Products\Products.Common\Products.Common.csproj", "{5E571774-1D77-4F79-AF04-27A85DAC1340}" 31 | EndProject 32 | Global 33 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 34 | Debug|Any CPU = Debug|Any CPU 35 | Release|Any CPU = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 38 | {D3478255-CB14-4F5D-AC78-2E48B5EC2439}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {D3478255-CB14-4F5D-AC78-2E48B5EC2439}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {D3478255-CB14-4F5D-AC78-2E48B5EC2439}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {D3478255-CB14-4F5D-AC78-2E48B5EC2439}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {B1ECFB12-A337-49FF-AD09-304B5292A548}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {B1ECFB12-A337-49FF-AD09-304B5292A548}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {B1ECFB12-A337-49FF-AD09-304B5292A548}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {B1ECFB12-A337-49FF-AD09-304B5292A548}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {2D8DF944-BAC5-4CE1-807A-6FFCE3C51284}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {2D8DF944-BAC5-4CE1-807A-6FFCE3C51284}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {2D8DF944-BAC5-4CE1-807A-6FFCE3C51284}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {2D8DF944-BAC5-4CE1-807A-6FFCE3C51284}.Release|Any CPU.Build.0 = Release|Any CPU 50 | {FDED84A3-EC77-42E1-BDAA-E5A2A56E800F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 51 | {FDED84A3-EC77-42E1-BDAA-E5A2A56E800F}.Debug|Any CPU.Build.0 = Debug|Any CPU 52 | {FDED84A3-EC77-42E1-BDAA-E5A2A56E800F}.Release|Any CPU.ActiveCfg = Release|Any CPU 53 | {FDED84A3-EC77-42E1-BDAA-E5A2A56E800F}.Release|Any CPU.Build.0 = Release|Any CPU 54 | {FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 55 | {FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}.Debug|Any CPU.Build.0 = Debug|Any CPU 56 | {FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}.Release|Any CPU.ActiveCfg = Release|Any CPU 57 | {FC24D38F-FB96-43E1-B21E-B6E1BFA1DC11}.Release|Any CPU.Build.0 = Release|Any CPU 58 | {D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 59 | {D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}.Debug|Any CPU.Build.0 = Debug|Any CPU 60 | {D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}.Release|Any CPU.ActiveCfg = Release|Any CPU 61 | {D3D97918-2C26-4BAE-A0C5-F90B6F1085DD}.Release|Any CPU.Build.0 = Release|Any CPU 62 | {C46D3E71-E08D-4F45-B54B-03E3759028E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 63 | {C46D3E71-E08D-4F45-B54B-03E3759028E3}.Debug|Any CPU.Build.0 = Debug|Any CPU 64 | {C46D3E71-E08D-4F45-B54B-03E3759028E3}.Release|Any CPU.ActiveCfg = Release|Any CPU 65 | {C46D3E71-E08D-4F45-B54B-03E3759028E3}.Release|Any CPU.Build.0 = Release|Any CPU 66 | {5E571774-1D77-4F79-AF04-27A85DAC1340}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 67 | {5E571774-1D77-4F79-AF04-27A85DAC1340}.Debug|Any CPU.Build.0 = Debug|Any CPU 68 | {5E571774-1D77-4F79-AF04-27A85DAC1340}.Release|Any CPU.ActiveCfg = Release|Any CPU 69 | {5E571774-1D77-4F79-AF04-27A85DAC1340}.Release|Any CPU.Build.0 = Release|Any CPU 70 | EndGlobalSection 71 | GlobalSection(SolutionProperties) = preSolution 72 | HideSolutionNode = FALSE 73 | EndGlobalSection 74 | GlobalSection(NestedProjects) = preSolution 75 | {D3478255-CB14-4F5D-AC78-2E48B5EC2439} = {996FE8C5-4EBF-4CF9-9A6C-7A21F23A4B94} 76 | {B1ECFB12-A337-49FF-AD09-304B5292A548} = {BB388DD9-BEE8-4755-952E-0F51CA60AAC1} 77 | {2D8DF944-BAC5-4CE1-807A-6FFCE3C51284} = {E4FA608F-F4B2-41AA-B819-3BAC53839407} 78 | {FDED84A3-EC77-42E1-BDAA-E5A2A56E800F} = {E4FA608F-F4B2-41AA-B819-3BAC53839407} 79 | {A7C685A0-C705-475B-9A28-059C842A5224} = {1F0F6FA5-1887-48E5-9E89-A195CAF8429A} 80 | {C46D3E71-E08D-4F45-B54B-03E3759028E3} = {A7C685A0-C705-475B-9A28-059C842A5224} 81 | {5E571774-1D77-4F79-AF04-27A85DAC1340} = {A7C685A0-C705-475B-9A28-059C842A5224} 82 | EndGlobalSection 83 | GlobalSection(ExtensibilityGlobals) = postSolution 84 | SolutionGuid = {821DEB2E-922C-4FC9-9BF2-D45E9FEA13AB} 85 | EndGlobalSection 86 | EndGlobal 87 | -------------------------------------------------------------------------------- /Sales/Sales.Common/Dto/OrderDto.cs: -------------------------------------------------------------------------------- 1 | using MicroServices.Common; 2 | using System; 3 | 4 | namespace Sales.Common.Dto 5 | { 6 | public class OrderDto : ReadObject 7 | { 8 | public string ProductName { get; set; } 9 | public int Quantity { get; set; } 10 | public bool IsPaidFor { get; set; } 11 | public int Version { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /Sales/Sales.Common/Events/OrderEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.CodeDom; 3 | using Newtonsoft.Json; 4 | using MicroServices.Common; 5 | 6 | namespace Sales.Common.Events 7 | { 8 | public class OrderPlaced : Event 9 | { 10 | public OrderPlaced(Guid id, Guid productId, int quantity) 11 | { 12 | Id = id; 13 | ProductId = productId; 14 | Quantity = quantity; 15 | } 16 | 17 | [JsonConstructor] 18 | private OrderPlaced(Guid id, Guid productId, int quantity, int version) : this(id, productId, quantity) 19 | { 20 | Version = version; 21 | } 22 | 23 | public Guid ProductId { get; private set; } 24 | public int Quantity { get; private set; } 25 | } 26 | 27 | public class OrderPaidFor : Event 28 | { 29 | public OrderPaidFor(Guid id) 30 | { 31 | Id = id; 32 | } 33 | 34 | [JsonConstructor] 35 | private OrderPaidFor(Guid id, int version) : this(id) 36 | { 37 | Version = version; 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Sales/Sales.Common/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | 8 | [assembly: AssemblyTitle("Sales.Common")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Sales.Common")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 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 | 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | 25 | [assembly: Guid("d3478255-cb14-4f5d-ac78-2e48b5ec2439")] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [assembly: AssemblyVersion("1.0.*")] 37 | 38 | [assembly: AssemblyVersion("1.0.0.0")] 39 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /Sales/Sales.Common/Sales.Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {D3478255-CB14-4F5D-AC78-2E48B5EC2439} 8 | Library 9 | Properties 10 | Sales.Common 11 | Sales.Common 12 | v4.6.2 13 | 512 14 | 15 | ..\ 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\NewId.3.0.1\lib\net452\NewId.dll 37 | 38 | 39 | ..\packages\Newtonsoft.Json.10.0.3\lib\net45\Newtonsoft.Json.dll 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | {fc24d38f-fb96-43e1-b21e-b6e1bfa1dc11} 62 | MicroServices.Common.General 63 | 64 | 65 | {d3d97918-2c26-4bae-a0c5-f90b6f1085dd} 66 | MicroServices.Common 67 | 68 | 69 | 70 | 77 | -------------------------------------------------------------------------------- /Sales/Sales.Common/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Sales/Sales.Common/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /Sales/Sales.ReadModels.Service/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Sales/Sales.ReadModels.Service/Config/AppConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | namespace Sales.ReadModels.Service.Config 4 | { 5 | public class AppConfiguration : ConfigurationSection 6 | { 7 | public static AppConfiguration Config 8 | { 9 | get { return ConfigurationManager.GetSection("appConfiguration") as AppConfiguration; } 10 | } 11 | 12 | [ConfigurationProperty("serviceDescription")] 13 | public string ServiceDescription 14 | { 15 | get { return (string) this["serviceDescription"]; } 16 | set { this["serviceDescription"] = value; } 17 | } 18 | 19 | [ConfigurationProperty("serviceDisplayName")] 20 | public string ServiceDisplayName 21 | { 22 | get { return (string) this["serviceDisplayName"]; } 23 | set { this["serviceDisplayName"] = value; } 24 | } 25 | 26 | [ConfigurationProperty("serviceName")] 27 | public string ServiceName 28 | { 29 | get { return (string) this["serviceName"]; } 30 | set { this["serviceName"] = value; } 31 | } 32 | 33 | [ConfigurationProperty("serviceUri")] 34 | public string ServiceUri 35 | { 36 | get { return (string) this["serviceUri"]; } 37 | set { this["serviceUri"] = value; } 38 | } 39 | 40 | [ConfigurationProperty("messageBusEndPoint")] 41 | public string MessageBusEndPoint 42 | { 43 | get { return (string) this["messageBusEndPoint"]; } 44 | set { this["messageBusEndPoint"] = value; } 45 | } 46 | 47 | [ConfigurationProperty("eventStorePort")] 48 | public int EventStorePort 49 | { 50 | get { return (int) this["eventStorePort"]; } 51 | set { this["eventStorePort"] = value; } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Sales/Sales.ReadModels.Service/Controllers/OrdersController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Web.Http; 3 | using MicroServices.Common.Exceptions; 4 | 5 | namespace Sales.ReadModels.Service.Controllers 6 | { 7 | public class OrdersController : ApiController 8 | { 9 | [HttpGet] 10 | public IHttpActionResult Get(Guid id) 11 | { 12 | var view = ServiceLocator.BrandView; 13 | try 14 | { 15 | var dto = view.GetById(id); 16 | return Ok(dto); 17 | } 18 | catch (ReadModelNotFoundException) 19 | { 20 | return NotFound(); 21 | } 22 | } 23 | 24 | [HttpGet] 25 | public IHttpActionResult Get() 26 | { 27 | var view = ServiceLocator.BrandView; 28 | var result = view.GetAll(); 29 | return Ok(result); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Sales/Sales.ReadModels.Service/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Owin.Hosting; 3 | using Sales.ReadModels.Service.Config; 4 | using Topshelf; 5 | 6 | namespace Sales.ReadModels.Service 7 | { 8 | internal static class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | HostFactory.Run(x => 13 | { 14 | x.Service(s => 15 | { 16 | s.ConstructUsing(name => new SalesReadModelsService()); 17 | s.WhenStarted(tc => tc.Start()); 18 | s.WhenStopped(tc => tc.Stop()); 19 | }); 20 | x.RunAsLocalSystem(); 21 | 22 | x.SetDescription("Sales - Read Models Service"); 23 | x.SetDisplayName("Sales - Read Models Service"); 24 | x.SetServiceName("Sales.ReadModels.Service"); 25 | }); 26 | } 27 | } 28 | 29 | public class SalesReadModelsService 30 | { 31 | private IDisposable webApp; 32 | 33 | public void Start() 34 | { 35 | var baseUri = "http://localhost:8182"; 36 | 37 | Console.WriteLine("Starting Sales Read Model Service..."); 38 | webApp = WebApp.Start(baseUri); 39 | Console.WriteLine("Server running at {0} - press Enter to quit. ", baseUri); 40 | } 41 | 42 | public void Stop() 43 | { 44 | webApp.Dispose(); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /Sales/Sales.ReadModels.Service/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | 8 | [assembly: AssemblyTitle("Sales.ReadModels.Service")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Sales.ReadModels.Service")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 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 | 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | 25 | [assembly: Guid("b1ecfb12-a337-49ff-ad09-304b5292a548")] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [assembly: AssemblyVersion("1.0.*")] 37 | 38 | [assembly: AssemblyVersion("1.0.0.0")] 39 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /Sales/Sales.ReadModels.Service/ServiceLocator.cs: -------------------------------------------------------------------------------- 1 | using Sales.ReadModels.Service.Views; 2 | using MicroServices.Common.MessageBus; 3 | using StackExchange.Redis; 4 | 5 | namespace Sales.ReadModels.Service 6 | { 7 | public static class ServiceLocator 8 | { 9 | public static IMessageBus Bus { get; set; } 10 | public static OrderView BrandView { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /Sales/Sales.ReadModels.Service/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Web.Http; 4 | using Owin; 5 | using Sales.ReadModels.Service.Views; 6 | using MicroServices.Common.Exceptions; 7 | using MicroServices.Common.MessageBus; 8 | using MicroServices.Common.Repository; 9 | using EasyNetQ; 10 | using Newtonsoft.Json; 11 | using Sales.Common.Dto; 12 | using MicroServices.Common; 13 | using MicroServices.Common.General.Util; 14 | using StackExchange.Redis; 15 | using Aggregate = MicroServices.Common.Aggregate; 16 | 17 | namespace Sales.ReadModels.Service 18 | { 19 | internal class Startup 20 | { 21 | public void Configuration(IAppBuilder app) 22 | { 23 | var webApiConfiguration = ConfigureWebApi(); 24 | app.UseWebApi(webApiConfiguration); 25 | ConfigureHandlers(); 26 | } 27 | 28 | private static HttpConfiguration ConfigureWebApi() 29 | { 30 | var config = new HttpConfiguration(); 31 | config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new { id = RouteParameter.Optional }); 32 | return config; 33 | } 34 | 35 | private void ConfigureHandlers() 36 | { 37 | var redis = ConnectionMultiplexer.Connect("localhost"); 38 | var brandView = new OrderView(new RedisReadModelRepository(redis.GetDatabase())); 39 | ServiceLocator.BrandView = brandView; 40 | 41 | var eventMappings = new EventHandlerDiscovery() 42 | .Scan(brandView) 43 | .Handlers; 44 | 45 | var messageBusEndPoint = "Sales_readmodel"; 46 | var topicFilter = "Sales.Common.Events"; 47 | 48 | var b = RabbitHutch.CreateBus("host=localhost"); 49 | 50 | b.Subscribe(messageBusEndPoint, 51 | m => 52 | { 53 | Aggregate handler; 54 | var messageType = Type.GetType(m.MessageTypeName); 55 | var handlerFound = eventMappings.TryGetValue(messageType, out handler); 56 | if (handlerFound) 57 | { 58 | var @event = JsonConvert.DeserializeObject(m.SerialisedMessage, messageType); 59 | handler.AsDynamic().ApplyEvent(@event, ((Event)@event).Version); 60 | } 61 | }, 62 | q => q.WithTopic(topicFilter)); 63 | 64 | var bus = new RabbitMqBus(b); 65 | 66 | ServiceLocator.Bus = bus; 67 | } 68 | } 69 | } -------------------------------------------------------------------------------- /Sales/Sales.ReadModels.Service/Views/OrderView.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Security.Cryptography.X509Certificates; 5 | using Newtonsoft.Json; 6 | using Sales.Common.Dto; 7 | using Sales.Common.Events; 8 | using MicroServices.Common; 9 | using MicroServices.Common.Exceptions; 10 | using MicroServices.Common.Repository; 11 | using StackExchange.Redis; 12 | using Products.ReadModels.Client; 13 | using Sales.Service.MicroServices.Product.View; 14 | 15 | namespace Sales.ReadModels.Service.Views 16 | { 17 | public class OrderView : ReadModelAggregate, 18 | IHandle, 19 | IHandle 20 | { 21 | private readonly IReadModelRepository repository; 22 | 23 | public OrderView(IReadModelRepository repository) 24 | { 25 | this.repository = repository; 26 | } 27 | 28 | public OrderDto GetById(Guid id) 29 | { 30 | try 31 | { 32 | return repository.Get(id); 33 | } 34 | catch 35 | { 36 | throw new ReadModelNotFoundException(id, typeof(OrderDto)); 37 | } 38 | } 39 | 40 | public IEnumerable GetAll() 41 | { 42 | return repository.GetAll(); 43 | } 44 | 45 | public void Apply(OrderPlaced e) 46 | { 47 | var productView = new ProductView(); 48 | var product = productView.GetById(e.ProductId); 49 | var dto = new OrderDto 50 | { 51 | Id = e.Id, 52 | Quantity = e.Quantity, 53 | ProductName = product.Description, 54 | Version = e.Version, 55 | IsPaidFor = false 56 | }; 57 | repository.Insert(dto); 58 | } 59 | 60 | public void Apply(OrderPaidFor e) 61 | { 62 | var order = GetById(e.Id); 63 | order.Version = e.Version; 64 | order.IsPaidFor = true; 65 | repository.Update(order); 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /Sales/Sales.ReadModels.Service/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Sales/Sales.Service.Tests/OrderCommandTests.cs: -------------------------------------------------------------------------------- 1 | using Sales.Service.MicroServices.Order.Commands; 2 | using Sales.Service.MicroServices.Order.Domain; 3 | using Sales.Service.MicroServices.Order.Handlers; 4 | using MicroServices.Common.MessageBus; 5 | using MicroServices.Common.Repository; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using Xunit; 12 | 13 | namespace Sales.Service.Tests 14 | { 15 | public class OrderCommandTests 16 | { 17 | private readonly IMessageBus bus; 18 | private readonly IRepository repository; 19 | 20 | public OrderCommandTests() 21 | { 22 | bus = new InProcessMessageBus(); 23 | repository = new InMemoryRepository(bus); 24 | } 25 | 26 | [Fact] 27 | public void Should_fail_when_no_product_is_provided_and_a_new_order_is_started() 28 | { 29 | var command = new StartNewOrder(Guid.NewGuid(), Guid.Empty, 1); 30 | var handler = new OrderCommandHandlers(null, new FakeProductsProductView()); 31 | Assert.Throws(() => handler.Handle(command)); 32 | } 33 | 34 | [Fact] 35 | public void Should_fail_when_starting_a_new_order_and_product_quantity_is_zero() 36 | { 37 | var command = new StartNewOrder(Guid.NewGuid(), Guid.NewGuid(), 0); 38 | var handler = new OrderCommandHandlers(null, new FakeProductsProductView()); 39 | Assert.Throws(() => handler.Handle(command)); 40 | } 41 | 42 | [Fact] 43 | public void Should_succeed_when_starting_a_new_order_with_a_valid_product_and_quantity() 44 | { 45 | var id = Guid.NewGuid(); 46 | var ProductsProducts = new FakeProductsProductView(); 47 | var productId = ProductsProducts.GetProducts().First().Id; 48 | 49 | var command = new StartNewOrder(id, productId, 1); 50 | var handler = new OrderCommandHandlers(repository, ProductsProducts); 51 | handler.Handle(command); 52 | 53 | var order = repository.GetById(id); 54 | Assert.Equal(1, order.Quantity); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Sales/Sales.Service.Tests/Products.cs: -------------------------------------------------------------------------------- 1 | using Products.Common.Dto; 2 | using Products.ReadModels.Client; 3 | using Sales.Service.MicroServices.Product.View; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using Xunit; 10 | 11 | namespace Sales.Service.Tests 12 | { 13 | public class Products 14 | { 15 | readonly IProductsView ProductsProductView; 16 | readonly ProductView productView; 17 | 18 | public Products() 19 | { 20 | ProductsProductView = new FakeProductsProductView(); 21 | productView = new ProductView(ProductsProductView); 22 | } 23 | 24 | [Fact] 25 | public void Should_retrieve_a_list_of_all_products_via_Products_client_when_view_is_created() 26 | { 27 | Assert.Equal(2, productView.GetAll().Count()); 28 | } 29 | 30 | [Fact] 31 | public void Should_retrieve_product_when_given_a_known_product_id() 32 | { 33 | var id = productView.GetAll().First().Id; 34 | var p = productView.GetById(id); 35 | Assert.Equal(id, p.Id); 36 | } 37 | } 38 | 39 | public class FakeProductsProductView : IProductsView 40 | { 41 | private readonly List products = new List(); 42 | 43 | public FakeProductsProductView() 44 | { 45 | products.Add(new ProductDto 46 | { 47 | Id = Guid.NewGuid(), 48 | Name = "FW", 49 | DisplayName = "Flat White", 50 | Description = "Great Coffee", 51 | Price = 3.60m, 52 | Version = 1, 53 | }); 54 | products.Add(new ProductDto 55 | { 56 | Id = Guid.NewGuid(), 57 | Name = "BB", 58 | DisplayName = "Banana Bread", 59 | Description = "Delicious, slightly toasted goodness", 60 | Price = 4.70m, 61 | Version = 1, 62 | }); 63 | } 64 | 65 | public ProductDto GetById(Guid id) 66 | { 67 | throw new NotImplementedException(); 68 | } 69 | 70 | public IEnumerable GetProducts() 71 | { 72 | return products.AsEnumerable(); 73 | } 74 | 75 | public void Initialise() 76 | { 77 | throw new NotImplementedException(); 78 | } 79 | 80 | public void Initialize() 81 | { 82 | throw new NotImplementedException(); 83 | } 84 | 85 | public void Reset() 86 | { 87 | throw new NotImplementedException(); 88 | } 89 | 90 | public void UpdateLocalCache(ProductDto newValue) 91 | { 92 | throw new NotImplementedException(); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Sales/Sales.Service.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("Sales.Service.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Sales.Service.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 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("fded84a3-ec77-42e1-bdaa-e5a2a56e800f")] 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 | -------------------------------------------------------------------------------- /Sales/Sales.Service.Tests/Sales.Service.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Debug 8 | AnyCPU 9 | {FDED84A3-EC77-42E1-BDAA-E5A2A56E800F} 10 | Library 11 | Properties 12 | Sales.Service.Tests 13 | Sales.Service.Tests 14 | v4.6.2 15 | 512 16 | 17 | 18 | 19 | 20 | 21 | true 22 | full 23 | false 24 | bin\Debug\ 25 | DEBUG;TRACE 26 | prompt 27 | 4 28 | 29 | 30 | pdbonly 31 | true 32 | bin\Release\ 33 | TRACE 34 | prompt 35 | 4 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | ..\packages\xunit.abstractions.2.0.1\lib\net35\xunit.abstractions.dll 48 | 49 | 50 | ..\packages\xunit.assert.2.3.1\lib\netstandard1.1\xunit.assert.dll 51 | 52 | 53 | ..\packages\xunit.extensibility.core.2.3.1\lib\netstandard1.1\xunit.core.dll 54 | 55 | 56 | ..\packages\xunit.extensibility.execution.2.3.1\lib\net452\xunit.execution.desktop.dll 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | {fc24d38f-fb96-43e1-b21e-b6e1bfa1dc11} 71 | MicroServices.Common.General 72 | 73 | 74 | {d3d97918-2c26-4bae-a0c5-f90b6f1085dd} 75 | MicroServices.Common 76 | 77 | 78 | {5e571774-1d77-4f79-af04-27a85dac1340} 79 | Products.Common 80 | 81 | 82 | {c46d3e71-e08d-4f45-b54b-03e3759028e3} 83 | Products.ReadModels.Client 84 | 85 | 86 | {2d8df944-bac5-4ce1-807a-6ffce3c51284} 87 | Sales.Service 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 97 | 98 | 99 | 100 | 101 | 102 | 103 | 110 | -------------------------------------------------------------------------------- /Sales/Sales.Service.Tests/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Sales/Sales.Service.Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /Sales/Sales.Service/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Sales/Sales.Service/Config/AppConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.Configuration; 2 | 3 | namespace Sales.Service.Config 4 | { 5 | public class AppConfiguration : ConfigurationSection 6 | { 7 | public static AppConfiguration Config 8 | { 9 | get { return ConfigurationManager.GetSection("appConfiguration") as AppConfiguration; } 10 | } 11 | 12 | [ConfigurationProperty("serviceDescription")] 13 | public string ServiceDescription 14 | { 15 | get { return (string) this["serviceDescription"]; } 16 | set { this["serviceDescription"] = value; } 17 | } 18 | 19 | [ConfigurationProperty("serviceDisplayName")] 20 | public string ServiceDisplayName 21 | { 22 | get { return (string) this["serviceDisplayName"]; } 23 | set { this["serviceDisplayName"] = value; } 24 | } 25 | 26 | [ConfigurationProperty("serviceName")] 27 | public string ServiceName 28 | { 29 | get { return (string) this["serviceName"]; } 30 | set { this["serviceName"] = value; } 31 | } 32 | 33 | [ConfigurationProperty("serviceUri")] 34 | public string ServiceUri 35 | { 36 | get { return (string) this["serviceUri"]; } 37 | set { this["serviceUri"] = value; } 38 | } 39 | 40 | [ConfigurationProperty("messageBusEndPoint")] 41 | public string MessageBusEndPoint 42 | { 43 | get { return (string) this["messageBusEndPoint"]; } 44 | set { this["messageBusEndPoint"] = value; } 45 | } 46 | 47 | [ConfigurationProperty("eventStorePort")] 48 | public int EventStorePort 49 | { 50 | get { return (int) this["eventStorePort"]; } 51 | set { this["eventStorePort"] = value; } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Sales/Sales.Service/Controllers/OrdersController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Net; 3 | using System.Net.Http; 4 | using System.Web.Http; 5 | using Sales.Service.DataTransferObjects.Commands; 6 | using Sales.Service.MicroServices.Order.Commands; 7 | 8 | namespace Sales.Service.Controllers 9 | { 10 | public class OrdersController : ApiController 11 | { 12 | [HttpPost] 13 | public IHttpActionResult Post(PlaceOrderCommand cmd) 14 | { 15 | if (Guid.Empty.Equals(cmd.Id)) 16 | { 17 | var response = new HttpResponseMessage(HttpStatusCode.Forbidden) { 18 | Content = new StringContent("order information must be supplied in the POST body"), 19 | ReasonPhrase = "Missing Order Id" 20 | }; 21 | throw new HttpResponseException(response); 22 | } 23 | 24 | var command = new StartNewOrder(cmd.Id, cmd.ProductId, cmd.Quantity); 25 | 26 | try 27 | { 28 | ServiceLocator.OrderCommands.Handle(command); 29 | 30 | var link = new Uri(string.Format("http://localhost:8182/api/orders/{0}", command.Id)); 31 | return Created(link, command); 32 | } 33 | catch (ArgumentException argEx) 34 | { 35 | return BadRequest(argEx.Message); 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Sales/Sales.Service/DataTransferObjects/Commands/OrderCommands.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Sales.Service.DataTransferObjects.Commands 4 | { 5 | public class PlaceOrderCommand 6 | { 7 | public Guid Id { get; set; } 8 | public Guid ProductId { get; set; } 9 | public int Quantity { get; set; } 10 | } 11 | 12 | public class PayForOrderCommand 13 | { 14 | public string Code { get; set; } 15 | public string Name { get; set; } 16 | public int Version { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /Sales/Sales.Service/Helpers/ApiControllerHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http.Headers; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Web; 8 | using System.Web.Http; 9 | 10 | namespace Sales.Service.Controllers 11 | { 12 | public static class ApiControllerHelper 13 | { 14 | /// 15 | /// Try Getting Commit Version from Command Request Header 16 | /// 17 | /// 18 | /// 19 | /// 20 | public static bool TryGetVersionFromHeader(this ApiController apiController, out int version) 21 | { 22 | if (apiController.Request.Headers.IfMatch == null || !apiController.Request.Headers.IfMatch.Any()) 23 | { 24 | version = 0; 25 | return false; 26 | } 27 | 28 | EntityTagHeaderValue firstHeaderVal = apiController.Request.Headers.IfMatch.First(); 29 | 30 | string value = firstHeaderVal.Tag; 31 | if (value.StartsWith("\"")) 32 | { 33 | value = value.Substring(1); 34 | } 35 | if (value.EndsWith("\"")) 36 | { 37 | value = value.Substring(0, value.Length - 1); 38 | } 39 | 40 | return int.TryParse(value, out version); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Sales/Sales.Service/MicroServices/Order/Commands/OrderCommands.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MicroServices.Common; 3 | 4 | namespace Sales.Service.MicroServices.Order.Commands 5 | { 6 | public class StartNewOrder : ICommand 7 | { 8 | public StartNewOrder(Guid id, Guid productId, int quantity) 9 | { 10 | Id = id; 11 | ProductId = productId; 12 | Quantity = quantity; 13 | } 14 | 15 | public Guid Id { get; private set; } 16 | public Guid ProductId { get; private set; } 17 | public int Quantity { get; private set; } 18 | } 19 | 20 | public class PayForOrder : ICommand 21 | { 22 | public PayForOrder(Guid id, int version) 23 | { 24 | Id = id; 25 | Version = version; 26 | } 27 | 28 | public Guid Id { get; private set; } 29 | public int Version { get; set; } 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /Sales/Sales.Service/MicroServices/Order/Domain/Order.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using Sales.Common.Events; 4 | using MicroServices.Common; 5 | 6 | namespace Sales.Service.MicroServices.Order.Domain 7 | { 8 | public class Order : Aggregate 9 | { 10 | private Order() 11 | { 12 | } 13 | 14 | public Order(Guid id, Guid productId, int quantity) 15 | { 16 | if (quantity <= 0) throw new ArgumentOutOfRangeException("quantity", "quantity must be a number from 1 and up"); 17 | if (Guid.Empty.Equals(productId)) throw new ArgumentNullException("productId", "A valid Product Guid must be provided"); 18 | 19 | ApplyEvent(new OrderPlaced(id, productId, quantity)); 20 | } 21 | 22 | public Guid ProductId { get; private set; } 23 | public int Quantity { get; private set; } 24 | public bool HasBeenPaid { get; private set; } 25 | 26 | private void Apply(OrderPlaced e) 27 | { 28 | Id = e.Id; 29 | ProductId = e.ProductId; 30 | Quantity = e.Quantity; 31 | HasBeenPaid = false; 32 | } 33 | 34 | private void Apply(OrderPaidFor e) 35 | { 36 | HasBeenPaid = true; 37 | } 38 | 39 | public void PayForOrder(int originalVersion) 40 | { 41 | //can only update the current version of an aggregate 42 | ValidateVersion(originalVersion); 43 | 44 | ApplyEvent(new OrderPaidFor(Id)); 45 | } 46 | 47 | private void ValidateVersion(int version) 48 | { 49 | if (Version != version) 50 | { 51 | throw new ArgumentOutOfRangeException("version", "Invalid version specified: the version is out of sync."); 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /Sales/Sales.Service/MicroServices/Order/Handlers/OrderCommandHandlers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Sales.Service.MicroServices.Order.Commands; 3 | using MicroServices.Common.Repository; 4 | using Sales.Service.MicroServices.Product.View; 5 | using Products.ReadModels.Client; 6 | 7 | namespace Sales.Service.MicroServices.Order.Handlers 8 | { 9 | public class OrderCommandHandlers 10 | { 11 | private readonly ProductView productView; 12 | private readonly IRepository repository; 13 | 14 | public OrderCommandHandlers(IRepository repository) 15 | : this(repository, new ProductsView()) 16 | { 17 | } 18 | 19 | public OrderCommandHandlers(IRepository repository, IProductsView ProductsProductsView) 20 | { 21 | this.repository = repository; 22 | this.productView = new ProductView(ProductsProductsView); 23 | } 24 | 25 | public void Handle(StartNewOrder message) 26 | { 27 | ValidateProduct(message.ProductId); 28 | var order = new Domain.Order(message.Id, message.ProductId, message.Quantity); 29 | repository.Save(order); 30 | } 31 | 32 | public void Handle(PayForOrder message) 33 | { 34 | var order = repository.GetById(message.Id); 35 | int committableVersion = message.Version; 36 | order.PayForOrder(committableVersion); 37 | repository.Save(order); 38 | } 39 | 40 | void ValidateProduct(Guid productId) 41 | { 42 | if (productId != Guid.Empty) 43 | { 44 | try 45 | { 46 | productView.GetById(productId); 47 | } 48 | catch (Exception) 49 | { 50 | throw new ArgumentOutOfRangeException("productId", "Invalid product identifier specified: the product cannot be found."); 51 | } 52 | } 53 | } 54 | 55 | } 56 | } -------------------------------------------------------------------------------- /Sales/Sales.Service/MicroServices/Product/Domain/Product.cs: -------------------------------------------------------------------------------- 1 | using MicroServices.Common; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Sales.Service.MicroServices.Product.Domain 9 | { 10 | public class Product : ReadObject 11 | { 12 | public decimal Price { get; set; } 13 | public string Description { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Sales/Sales.Service/MicroServices/Product/ExternalEvents/ProductEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Newtonsoft.Json; 3 | using MicroServices.Common; 4 | 5 | namespace Sales.Service.MicroServices.Product.Events 6 | { 7 | // Exact same class exists in the service that raises this event 8 | public class ProductCreated : Event 9 | { 10 | public string Name { get; private set; } 11 | public string Description { get; private set; } 12 | public decimal Price { get; private set; } 13 | public ProductCreated(Guid id, string name, string description, decimal price) 14 | { 15 | Id = id; 16 | Name = name; 17 | Description = description; 18 | Price = price; 19 | } 20 | [JsonConstructor] 21 | private ProductCreated(Guid id, string name, string description, decimal price, int version) 22 | : this(id, name, description, price) 23 | { 24 | Version = version; 25 | } 26 | } 27 | 28 | public class ProductNameChanged : Event 29 | { 30 | public ProductNameChanged(Guid id, string newName) 31 | { 32 | Id = id; 33 | NewName = newName; 34 | } 35 | 36 | [JsonConstructor] 37 | private ProductNameChanged(Guid id, string newName, int version) : this(id, newName) 38 | { 39 | Version = version; 40 | } 41 | 42 | public string NewName { get; private set; } 43 | } 44 | 45 | public class ProductDescriptionChanged : Event 46 | { 47 | public ProductDescriptionChanged(Guid id, string newDescription) 48 | { 49 | Id = id; 50 | NewDescription = newDescription; 51 | } 52 | 53 | [JsonConstructor] 54 | private ProductDescriptionChanged(Guid id, string newDescription, int version) : this(id, newDescription) 55 | { 56 | Version = version; 57 | } 58 | 59 | public string NewDescription { get; private set; } 60 | } 61 | 62 | public class ProductPriceChanged : Event 63 | { 64 | public ProductPriceChanged(Guid id, decimal newPrice) 65 | { 66 | Id = id; 67 | NewPrice = newPrice; 68 | } 69 | 70 | [JsonConstructor] 71 | private ProductPriceChanged(Guid id, decimal newPrice, int version) 72 | : this(id, newPrice) 73 | { 74 | Version = version; 75 | } 76 | 77 | public decimal NewPrice { get; private set; } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Sales/Sales.Service/MicroServices/Product/Handlers/ProductEventsHandler.cs: -------------------------------------------------------------------------------- 1 | using MicroServices.Common; 2 | using Sales.Service.MicroServices.Product.Events; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace Sales.Service.MicroServices.Product.Handlers 10 | { 11 | public class ProductEventsHandler : Aggregate, 12 | IHandle, 13 | IHandle 14 | { 15 | public void Apply(ProductCreated @event) 16 | { 17 | var view = ServiceLocator.ProductView; 18 | view.Add(@event.Id, @event.Price); 19 | } 20 | 21 | public void Apply(ProductPriceChanged @event) 22 | { 23 | var view = ServiceLocator.ProductView; 24 | var product = view.GetById(@event.Id); 25 | product.Price = @event.NewPrice; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Sales/Sales.Service/MicroServices/Product/View/ProductView.cs: -------------------------------------------------------------------------------- 1 | using MicroServices.Common.General.Util; 2 | using System; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Sales.Service.MicroServices.Product.Domain; 9 | 10 | namespace Sales.Service.MicroServices.Product.View 11 | { 12 | public class ProductView 13 | { 14 | private readonly Products.ReadModels.Client.IProductsView ProductsProducts; 15 | private readonly ConcurrentDictionary products = new ConcurrentDictionary(); 16 | 17 | public ProductView(Products.ReadModels.Client.IProductsView ProductsProductView) 18 | { 19 | ProductsProducts = ProductsProductView; 20 | if (products.Count == 0) 21 | { 22 | InitialiseProducts(); 23 | } 24 | } 25 | 26 | public ProductView() : this(new Products.ReadModels.Client.ProductsView()) { } 27 | 28 | public IEnumerable GetAll() 29 | { 30 | return products.Values.AsEnumerable(); 31 | } 32 | 33 | private void InitialiseProducts() 34 | { 35 | var transformedDtos = ProductsProducts.GetProducts().Select(p => new Domain.Product 36 | { 37 | Id = p.Id, 38 | Price = p.Price 39 | }); 40 | foreach (var p in transformedDtos) 41 | { 42 | products.TryAdd(p.Id, p); 43 | } 44 | } 45 | 46 | internal void Add(Guid id, decimal price) 47 | { 48 | var success = products.TryAdd(id, new Domain.Product() { Id = id, Price = price }); 49 | if (!success) 50 | { 51 | //Assume product is already present from a previous event (not sure if it's possible) 52 | //So we'll just update the price instead. 53 | var p = GetById(id); 54 | p.Price = price; 55 | } 56 | } 57 | 58 | public Domain.Product GetById(Guid id) 59 | { 60 | Domain.Product product; 61 | if (products.TryGetValue(id, out product)) return product; 62 | 63 | throw new ArgumentOutOfRangeException("id","A product with the id of " + id.ToString() + "couldn't be found"); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Sales/Sales.Service/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.Owin.Hosting; 3 | using Sales.Service.Config; 4 | using Topshelf; 5 | 6 | namespace Sales.Service 7 | { 8 | public static class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | HostFactory.Run(x => 13 | { 14 | x.Service(s => 15 | { 16 | s.ConstructUsing(name => new SalesService()); 17 | s.WhenStarted(tc => tc.Start()); 18 | s.WhenStopped(tc => tc.Stop()); 19 | }); 20 | x.RunAsLocalSystem(); 21 | 22 | x.SetDescription("Sales - Domain Service"); 23 | x.SetDisplayName("Sales - Domain Service"); 24 | x.SetServiceName("Sales.Service"); 25 | }); 26 | } 27 | } 28 | 29 | public class SalesService 30 | { 31 | private IDisposable webApp; 32 | 33 | public void Start() 34 | { 35 | var baseUri = "http://localhost:8183"; 36 | 37 | Console.WriteLine("Starting Sales Domain Service..."); 38 | webApp = WebApp.Start(baseUri); 39 | Console.WriteLine("Server running at {0} - press Enter to quit. ", baseUri); 40 | } 41 | 42 | public void Stop() 43 | { 44 | webApp.Dispose(); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /Sales/Sales.Service/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.InteropServices; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | 8 | [assembly: AssemblyTitle("Sales.Service")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("Sales.Service")] 13 | [assembly: AssemblyCopyright("Copyright © 2015")] 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 | 21 | [assembly: ComVisible(false)] 22 | 23 | // The following GUID is for the ID of the typelib if this project is exposed to COM 24 | 25 | [assembly: Guid("2d8df944-bac5-4ce1-807a-6ffce3c51284")] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [assembly: AssemblyVersion("1.0.*")] 37 | 38 | [assembly: AssemblyVersion("1.0.0.0")] 39 | [assembly: AssemblyFileVersion("1.0.0.0")] -------------------------------------------------------------------------------- /Sales/Sales.Service/ServiceLocator.cs: -------------------------------------------------------------------------------- 1 | using Sales.Service.MicroServices.Order.Handlers; 2 | using Sales.Service.MicroServices.Product.View; 3 | using MicroServices.Common.MessageBus; 4 | 5 | namespace Sales.Service 6 | { 7 | public static class ServiceLocator 8 | { 9 | public static IMessageBus Bus { get; set; } 10 | public static OrderCommandHandlers OrderCommands { get; set; } 11 | public static ProductView ProductView { get; set; } 12 | } 13 | } -------------------------------------------------------------------------------- /Sales/Sales.Service/Startup.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Web.Http; 3 | using EventStore.ClientAPI; 4 | using Owin; 5 | using Sales.Service.Config; 6 | using Sales.Service.MicroServices.Order.Handlers; 7 | using MicroServices.Common.MessageBus; 8 | using MicroServices.Common.Repository; 9 | using EasyNetQ; 10 | using MicroServices.Common.General.Util; 11 | using Sales.Service.MicroServices.Product.Handlers; 12 | using MicroServices.Common; 13 | using System; 14 | using Newtonsoft.Json; 15 | using Sales.Service.MicroServices.Product.View; 16 | 17 | namespace Sales.Service 18 | { 19 | internal class Startup 20 | { 21 | public void Configuration(IAppBuilder app) 22 | { 23 | var webApiConfiguration = ConfigureWebApi(); 24 | app.UseWebApi(webApiConfiguration); 25 | ConfigureHandlers(); 26 | } 27 | 28 | private static HttpConfiguration ConfigureWebApi() 29 | { 30 | var config = new HttpConfiguration(); 31 | 32 | // Enable Web API Attribute Routing 33 | config.MapHttpAttributeRoutes(); 34 | 35 | config.Routes.MapHttpRoute("DefaultApi", "api/{controller}/{id}", new {id = RouteParameter.Optional}); 36 | return config; 37 | } 38 | 39 | private void ConfigureHandlers() 40 | { 41 | var b = RabbitHutch.CreateBus("host=localhost"); 42 | var bus = new RabbitMqBus(b); 43 | ServiceLocator.Bus = bus; 44 | 45 | var messageBusEndPoint = "Sales_service"; 46 | var topicFilter = "Products.Common.Events"; 47 | 48 | var eventStorePort = 12900; 49 | 50 | var eventStoreConnection = EventStoreConnection.Create(new IPEndPoint(IPAddress.Loopback, eventStorePort)); 51 | eventStoreConnection.ConnectAsync().Wait(); 52 | var repository = new EventStoreRepository(eventStoreConnection, bus); 53 | 54 | ServiceLocator.OrderCommands = new OrderCommandHandlers(repository); 55 | ServiceLocator.ProductView = new ProductView(); 56 | 57 | var eventMappings = new EventHandlerDiscovery() 58 | .Scan(new ProductEventsHandler()) 59 | .Handlers; 60 | 61 | b.Subscribe(messageBusEndPoint, 62 | m => 63 | { 64 | Aggregate handler; 65 | var messageType = Type.GetType(m.MessageTypeName); 66 | var handlerFound = eventMappings.TryGetValue(messageType, out handler); 67 | if (handlerFound) 68 | { 69 | var @event = JsonConvert.DeserializeObject(m.SerialisedMessage, messageType); 70 | handler.AsDynamic().ApplyEvent(@event, ((Event)@event).Version); 71 | } 72 | }, 73 | q => q.WithTopic(topicFilter)); 74 | 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /Sales/Sales.Service/config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ExtHttpPort: 2802 3 | IntHttpPort: 2803 4 | ClusterGossipPort: 38702 5 | ExtTcpPort: 3902 6 | IntTcpPort: 3903 7 | Db: ..\Cashier\Data 8 | Log: ..\Cashier\Logs 9 | DiscoverViaDns: false 10 | ClusterSize: 1 11 | RunProjections: none 12 | --- -------------------------------------------------------------------------------- /Sales/Sales.Service/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /Start-EventStore.ps1: -------------------------------------------------------------------------------- 1 | Param( 2 | $eventStoreServerLocation = "c:\eventstore\server" 3 | ) 4 | 5 | $ErrorActionPreference = "inquire" 6 | 7 | If (-NOT ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")) 8 | { 9 | $arguments = "-file """ + $myinvocation.mycommand.definition + """ -eventStoreServerLocation """ + $eventStoreServerLocation + """" 10 | Start-Process "$psHome\powershell.exe" -Verb runAs -ArgumentList $arguments 11 | break 12 | } 13 | 14 | Function Get-ScriptDirectory { 15 | Split-Path -parent $PSCommandPath 16 | } 17 | 18 | Function StartEventStore($configFile) 19 | { 20 | $srcFolder = Get-ScriptDirectory 21 | $configFullPath = Join-Path $srcFolder -childpath $configFile; 22 | $eventStoreExe = "$eventStoreServerLocation\EventStore.ClusterNode.exe"; 23 | $arguments = "--config=`"$configFullPath`""; 24 | Start-Process -WorkingDirectory $eventStoreServerLocation -FilePath $eventStoreExe -ArgumentList $arguments 25 | } 26 | 27 | StartEventStore("config.yml"); 28 | -------------------------------------------------------------------------------- /Start-MicroServices.ps1: -------------------------------------------------------------------------------- 1 | #requires -version 3 2 | [CmdletBinding()] 3 | param ([bool]$monitorServices = $true) 4 | 5 | [console]::TreatControlCAsInput = $true 6 | 7 | function Invoke-MicroService { 8 | param ( 9 | $SourcePath, 10 | $DeployPath, 11 | $ProcessFilePath 12 | ) 13 | 14 | Copy-Item -Path $SourcePath -Destination $DeployPath -Recurse -ErrorAction Stop 15 | 16 | $Process = Start-Process -FilePath $ProcessFilePath -PassThru -ErrorAction Stop 17 | 18 | return $Process 19 | } 20 | 21 | function Stop-MicroService { 22 | param ( 23 | $Process 24 | ) 25 | 26 | if (-not $Process) { return } 27 | 28 | $Process.Refresh() 29 | if (-not $Process.HasExited) { 30 | $Process.CloseMainWindow() | Out-Null # todo consider PostMessage to window with Ctrl+C 31 | Start-Sleep -Seconds 1 32 | $Process.Refresh() 33 | } 34 | if (-not $Process.HasExited) { 35 | $Process.Kill() | Out-Null 36 | Start-Sleep -Seconds 2 37 | $Process.Refresh() 38 | } 39 | if (-not $Process.HasExited) { 40 | throw "could not stop process" 41 | } 42 | } 43 | 44 | function Find-MicroService { 45 | param ( 46 | $ProcessFilePath 47 | ) 48 | 49 | $ProcessBaseName = [System.IO.Path]::GetFileNameWithoutExtension($ProcessFilePath) 50 | $Process = Get-Process -Name $ProcessBaseName -ErrorAction SilentlyContinue | 51 | Where-Object { $_.Path -eq $ProcessFilePath } 52 | 53 | if (@($Process).Length -gt 1) { throw "More than one instance of the same microservice running" } 54 | 55 | return $Process 56 | } 57 | 58 | function Test-MicroService { 59 | param ( 60 | $Process 61 | ) 62 | 63 | if ($Process) { 64 | $Process.Refresh() 65 | return (-not $Process.HasExited) 66 | } 67 | return $false 68 | 69 | } 70 | 71 | 72 | function Get-LastWriteTime { 73 | param ( 74 | [Parameter(Mandatory)] 75 | $Path 76 | ) 77 | Get-ChildItem -Path $Path -Recurse | 78 | Measure-Object -Property LastWriteTime -Maximum | 79 | Select-Object -ExpandProperty Maximum 80 | } 81 | 82 | function New-MicroServiceObject { 83 | param ( 84 | $ProjectName, 85 | $RootPath 86 | ) 87 | 88 | $ServiceHostProcessFileName = $ProjectName + '.exe' 89 | 90 | $o = [pscustomobject]@{ 91 | Name = $ProjectName 92 | SourcePath = Join-Path -Path $PSScriptRoot -ChildPath $RootPath\$ProjectName\bin\Debug 93 | ProcessFileName = $ServiceHostProcessFileName 94 | Process = [System.Diagnostics.Process]$null 95 | DeployedLastWriteTime = [DateTime]::MinValue 96 | DeployPath = '' 97 | ProcessFilePath = '' 98 | } 99 | 100 | $o.DeployPath = Join-Path -Path (Split-Path -Path $o.SourcePath) -ChildPath Deployed 101 | $o.ProcessFilePath = Join-Path -Path $o.DeployPath -ChildPath $o.ProcessFileName 102 | 103 | $o.Process = Find-MicroService -ProcessFilePath $o.ProcessFilePath 104 | 105 | return $o 106 | } 107 | 108 | function Poll { 109 | param ( 110 | $MicroServiceData 111 | ) 112 | 113 | $ShouldDeploy = $false 114 | $LastWriteTime = Get-LastWriteTime -Path $MicroServiceData.SourcePath 115 | if (-not (Test-MicroService -Process $MicroServiceData.Process)) { 116 | $ShouldDeploy = $true 117 | } else { 118 | if ($LastWriteTime -gt $MicroServiceData.DeployedLastWriteTime) { 119 | $ShouldDeploy = $true 120 | } 121 | } 122 | 123 | if (-not $ShouldDeploy) { 124 | return 125 | } 126 | 127 | "$(Get-Date): Redeploying '$($MicroServiceData.Name)'" 128 | 129 | Stop-MicroService -Process $MicroServiceData.Process 130 | 131 | if (Test-Path -Path $MicroServiceData.DeployPath) { 132 | Remove-Item -Path $MicroServiceData.DeployPath -Recurse 133 | if (-not $?) { 134 | return 135 | } 136 | } 137 | 138 | $MicroServiceData.Process = Invoke-MicroService -SourcePath $MicroServiceData.SourcePath -DeployPath $MicroServiceData.DeployPath -ProcessFilePath $MicroServiceData.ProcessFilePath 139 | 140 | $MicroServiceData.DeployedLastWriteTime = $LastWriteTime 141 | } 142 | 143 | $MicroServices = @( 144 | New-MicroServiceObject -RootPath Admin -ProjectName Admin.Service 145 | New-MicroServiceObject -RootPath Admin -ProjectName Admin.ReadModels.Service 146 | New-MicroServiceObject -RootPath Cashier -ProjectName Cashier.ReadModels.Service 147 | New-MicroServiceObject -RootPath Cashier -ProjectName Cashier.Service 148 | New-MicroServiceObject -RootPath Barista -ProjectName Barista.Service 149 | ) 150 | 151 | while ($true) { 152 | if ($Host.UI.RawUI.KeyAvailable -and (3 -eq [int]$Host.UI.RawUI.ReadKey("AllowCtrlC,IncludeKeyUp,NoEcho").Character)) { 153 | "$(Get-Date): Shutdown requested" 154 | 155 | break 156 | } 157 | 158 | foreach ($MicroService in $MicroServices) { 159 | Poll -MicroServiceData $MicroService 160 | } 161 | 162 | if (-not $monitorServices) { 163 | return 164 | } 165 | 166 | Start-Sleep -Seconds 1 167 | } 168 | 169 | foreach ($MicroService in $MicroServices) { 170 | "$(Get-Date): Stopping '$($MicroService.Name)'" 171 | Stop-MicroService -Process $MicroService.Process 172 | } -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | --- 2 | ExtHttpPort: 12800 3 | IntHttpPort: 12801 4 | ClusterGossipPort: 12700 5 | ExtTcpPort: 12900 6 | IntTcpPort: 12901 7 | Db: ..\MicroCafe\Data 8 | Log: ..\MicroCafe\Logs 9 | DiscoverViaDns: false 10 | ClusterSize: 1 11 | --- -------------------------------------------------------------------------------- /redis.windows-service.conf: -------------------------------------------------------------------------------- 1 | maxheap 50m 2 | port 6379 3 | 4 | 5 | # save rdb file once a minute if anything has changed 6 | save 60 1 7 | dir c:\redis\data 8 | dbfilename data.rdb 9 | 10 | appendonly yes 11 | appendfilename data.aof 12 | 13 | # fsync - could lose a second of data - use 'always' for prod. 14 | appendfsync everysec --------------------------------------------------------------------------------