├── .gitignore ├── README.md ├── Workshop.Microservices.sln ├── docker-compose.yml └── src ├── ApiGateways └── Workshop.Microservices.Ocelot.ApiGateway │ ├── Dockerfile │ ├── Program.cs │ ├── Startup.cs │ ├── Workshop.Microservices.Ocelot.ApiGateway.csproj │ ├── appsettings.json │ └── ocelot.json ├── Infrastructure ├── Workshop.Microservices.Domain │ ├── Events │ │ └── SendInformationToServiceBEvent.cs │ └── Workshop.Microservices.Domain.csproj ├── Workshop.Microservices.EventBus.RabbitMq │ ├── DefaultRabbitMqPersistentConnection.cs │ ├── EventBusRabbitMq.cs │ ├── IRabbitMqPersistentConnection.cs │ └── Workshop.Microservices.EventBus.RabbitMq.csproj ├── Workshop.Microservices.EventBus │ ├── Abstractions │ │ ├── IDynamicIntegrationEventHandler.cs │ │ ├── IEventBus.cs │ │ └── IIntegrationEventHandler.cs │ ├── Events │ │ └── IntegrationEvent.cs │ ├── IEventBusSubscriptionsManager.cs │ ├── InMemoryEventBusSubscriptionsManager.cs │ ├── SubscriptionInfo.cs │ └── Workshop.Microservices.EventBus.csproj └── Workshop.Microservices.Extensions │ ├── AutoFacExtensions.cs │ ├── EventBusExtensions.cs │ ├── RabbitMqExtensions.cs │ └── Workshop.Microservices.Extensions.csproj └── Services ├── Workshop.Microservices.ServiceA ├── CommandHandlers │ └── ACommandHandler.cs ├── Commands │ └── ACommand.cs ├── Controllers │ └── ServiceAController.cs ├── Dockerfile ├── Events │ └── AEvent.cs ├── NotificationHandlers │ ├── AEventNotificationHandler.cs │ └── AnotherAEventNotificationHandler.cs ├── Program.cs ├── Startup.cs ├── Workshop.Microservices.ServiceA.csproj └── appsettings.json └── Workshop.Microservices.ServiceB ├── Controllers └── ServiceBController.cs ├── Dockerfile ├── IntegrationEvents └── EventHandling │ └── SendInformationToServiceBEventHandler.cs ├── Program.cs ├── Startup.cs ├── Workshop.Microservices.ServiceB.csproj └── appsettings.json /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microservices Training 2 | 3 | Sample microservice architecture on .net core 2.1 with rabbitMQ, mediatR and eventbus, also included ocelot api gateway and docker compose. 4 | 5 | ## Getting Started 6 | 7 | ### Using 8 | 9 | docker-compose up 10 | 11 | ## Api Gateway 12 | 13 | There is a json file which name is ocelot. You can find routing and aggregation rules in it. 14 | 15 | - **UpstreamPathTemplate** - Api gateway path. 16 | - **DownstreamPathTemplate** - Where to send. 17 | - **Aggregates** - Combines two or more service response into one call. 18 | 19 | ````json 20 | { 21 | "ReRoutes": [ 22 | { 23 | "DownstreamPathTemplate": "/api/service-a", 24 | "DownstreamScheme": "http", 25 | "DownstreamHostAndPorts": [ 26 | { 27 | "Host": "servicea", 28 | "Port": 5001 29 | } 30 | ], 31 | "UpstreamPathTemplate": "/api/a", 32 | "Key": "ServiceA" 33 | }, 34 | { 35 | "DownstreamPathTemplate": "/api/service-b", 36 | "DownstreamScheme": "http", 37 | "DownstreamHostAndPorts": [ 38 | { 39 | "Host": "serviceb", 40 | "Port": 5002 41 | } 42 | ], 43 | "UpstreamPathTemplate": "/api/b", 44 | "Key": "ServiceB" 45 | } 46 | ], 47 | "Aggregates": [ 48 | { 49 | "ReRouteKeys": [ 50 | "ServiceA", 51 | "ServiceB" 52 | ], 53 | "UpstreamPathTemplate": "/api/a-b" 54 | } 55 | ], 56 | "GlobalConfiguration": { 57 | "RequestIdKey": "OcRequestId" 58 | } 59 | } 60 | ```` 61 | 62 | ## EventBus Publishing Event 63 | 64 | There is an abstraction for event bus and rabbitmq client. Inspired from [eShopOnContainers](https://github.com/dotnet-architecture/eShopOnContainers). 65 | 66 | If you want to send an event to another microservices, first create an integration event. 67 | 68 | ````csharp 69 | public class SendInformationToServiceBEvent : IntegrationEvent 70 | { 71 | public string Information { get; set; } 72 | 73 | public SendInformationToServiceBEvent(string information) 74 | { 75 | Information = information; 76 | } 77 | } 78 | ```` 79 | Just publish this event from event bus. 80 | 81 | ````csharp 82 | _eventBus.Publish(new SendInformationToServiceBEvent("sending from service A")); 83 | ```` 84 | 85 | ## EventBus Subscribe Event 86 | 87 | Create an event handler for subscription. 88 | 89 | ````csharp 90 | public class SendInformationToServiceBEventHandler : IIntegrationEventHandler 91 | { 92 | public async Task Handle(SendInformationToServiceBEvent @event) 93 | { 94 | Console.WriteLine($"Event Id: {@event.Id} Creation Date: {@event.CreationDate} Message: {@event.Information}"); 95 | 96 | await Task.CompletedTask; 97 | } 98 | } 99 | ```` 100 | Don't forget to register event handlers. 101 | 102 | ````csharp 103 | services.AddSingleton(); 104 | ```` 105 | 106 | Finally, subscribe an event handler from event bus. 107 | 108 | ````csharp 109 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 110 | { 111 | app.UseMvc(); 112 | 113 | var eventBus = app.ApplicationServices.GetRequiredService(); 114 | 115 | eventBus.Subscribe(); 116 | } 117 | ```` 118 | 119 | ## Sending Commands with MediatR 120 | 121 | Create an event with mediatR. 122 | 123 | ````csharp 124 | public class ACommand : IRequest 125 | { 126 | public string Information { get; private set; } 127 | 128 | public ACommand(string information) 129 | { 130 | Information = information; 131 | } 132 | } 133 | ```` 134 | Create a handler which is related with event. 135 | 136 | ````csharp 137 | public class ACommandHandler : IRequestHandler 138 | { 139 | public async Task Handle(ACommand request, CancellationToken cancellationToken) 140 | { 141 | Console.WriteLine($"ACommandHandler Information: {request.Information} "); 142 | 143 | return true; 144 | } 145 | } 146 | ```` 147 | Send this command whatever you where. 148 | 149 | ````csharp 150 | await _mediator.Send(new ACommand("sending command from service A")); 151 | ```` 152 | 153 | ## Sending Notifications with MediatR 154 | 155 | Create a notification with mediatR. 156 | 157 | ````csharp 158 | public class AEvent : INotification 159 | { 160 | public string Information { get; private set; } 161 | 162 | public AEvent(string information) 163 | { 164 | Information = information; 165 | } 166 | } 167 | ```` 168 | 169 | Create handlers which are related with notification. 170 | 171 | ````csharp 172 | public class AEventNotificationHandler : INotificationHandler 173 | { 174 | public Task Handle(AEvent notification, CancellationToken cancellationToken) 175 | { 176 | Console.WriteLine($"AEventNotificationHandler Information: {notification.Information} "); 177 | 178 | return Task.FromResult(0); 179 | } 180 | } 181 | 182 | public class AnotherAEventNotificationHandler : INotificationHandler 183 | { 184 | public Task Handle(AEvent notification, CancellationToken cancellationToken) 185 | { 186 | Console.WriteLine($"AnotherAEventNotificationHandler Information: {notification.Information} "); 187 | 188 | return Task.FromResult(0); 189 | } 190 | } 191 | ```` 192 | 193 | Send this command whatever you where. 194 | 195 | ````csharp 196 | await _mediator.Publish(new AEvent("sending notification from service A")); 197 | ```` 198 | 199 | Ta-da!! 200 | -------------------------------------------------------------------------------- /Workshop.Microservices.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.136 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4EEA2F94-3AA8-4D14-8A99-0BBD57DFE0B8}" 7 | EndProject 8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Services", "Services", "{D7F89B94-6694-4244-8028-85448F3D5DD9}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Workshop.Microservices.ServiceA", "src\Services\Workshop.Microservices.ServiceA\Workshop.Microservices.ServiceA.csproj", "{31F15CEF-D08C-48FF-AE30-070F17FA77DB}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Workshop.Microservices.ServiceB", "src\Services\Workshop.Microservices.ServiceB\Workshop.Microservices.ServiceB.csproj", "{6B5FC3E5-596D-462E-8EA6-858503E76ABF}" 13 | EndProject 14 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ApiGateways", "ApiGateways", "{AF06E745-6C9D-4595-9801-796D03B5DA91}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Workshop.Microservices.Ocelot.ApiGateway", "src\ApiGateways\Workshop.Microservices.Ocelot.ApiGateway\Workshop.Microservices.Ocelot.ApiGateway.csproj", "{FDA25617-E118-4B45-9A8A-ACCE518F2EA0}" 17 | EndProject 18 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9B837746-3B48-4B6A-8BC2-355FB12A2E92}" 19 | ProjectSection(SolutionItems) = preProject 20 | docker-compose.yml = docker-compose.yml 21 | EndProjectSection 22 | EndProject 23 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Infrastructure", "Infrastructure", "{6DD9178D-8470-4449-BF66-7D09DDB20A7A}" 24 | EndProject 25 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Workshop.Microservices.EventBus", "src\Infrastructure\Workshop.Microservices.EventBus\Workshop.Microservices.EventBus.csproj", "{75EBCB31-AC5C-4EBD-826E-5BA56758A7E8}" 26 | EndProject 27 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Workshop.Microservices.EventBus.RabbitMq", "src\Infrastructure\Workshop.Microservices.EventBus.RabbitMq\Workshop.Microservices.EventBus.RabbitMq.csproj", "{E6BC350B-0460-4831-B737-CDD47EAB858B}" 28 | EndProject 29 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Workshop.Microservices.Extensions", "src\Infrastructure\Workshop.Microservices.Extensions\Workshop.Microservices.Extensions.csproj", "{ADD48CEB-8938-4F9A-80D7-F48BE536C094}" 30 | EndProject 31 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Workshop.Microservices.Domain", "src\Infrastructure\Workshop.Microservices.Domain\Workshop.Microservices.Domain.csproj", "{0A04082B-C276-4A55-9E43-56D483AC0E7C}" 32 | EndProject 33 | Global 34 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 35 | Debug|Any CPU = Debug|Any CPU 36 | Release|Any CPU = Release|Any CPU 37 | EndGlobalSection 38 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 39 | {31F15CEF-D08C-48FF-AE30-070F17FA77DB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {31F15CEF-D08C-48FF-AE30-070F17FA77DB}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {31F15CEF-D08C-48FF-AE30-070F17FA77DB}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {31F15CEF-D08C-48FF-AE30-070F17FA77DB}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {6B5FC3E5-596D-462E-8EA6-858503E76ABF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 44 | {6B5FC3E5-596D-462E-8EA6-858503E76ABF}.Debug|Any CPU.Build.0 = Debug|Any CPU 45 | {6B5FC3E5-596D-462E-8EA6-858503E76ABF}.Release|Any CPU.ActiveCfg = Release|Any CPU 46 | {6B5FC3E5-596D-462E-8EA6-858503E76ABF}.Release|Any CPU.Build.0 = Release|Any CPU 47 | {FDA25617-E118-4B45-9A8A-ACCE518F2EA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 48 | {FDA25617-E118-4B45-9A8A-ACCE518F2EA0}.Debug|Any CPU.Build.0 = Debug|Any CPU 49 | {FDA25617-E118-4B45-9A8A-ACCE518F2EA0}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {FDA25617-E118-4B45-9A8A-ACCE518F2EA0}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {75EBCB31-AC5C-4EBD-826E-5BA56758A7E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 52 | {75EBCB31-AC5C-4EBD-826E-5BA56758A7E8}.Debug|Any CPU.Build.0 = Debug|Any CPU 53 | {75EBCB31-AC5C-4EBD-826E-5BA56758A7E8}.Release|Any CPU.ActiveCfg = Release|Any CPU 54 | {75EBCB31-AC5C-4EBD-826E-5BA56758A7E8}.Release|Any CPU.Build.0 = Release|Any CPU 55 | {E6BC350B-0460-4831-B737-CDD47EAB858B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 56 | {E6BC350B-0460-4831-B737-CDD47EAB858B}.Debug|Any CPU.Build.0 = Debug|Any CPU 57 | {E6BC350B-0460-4831-B737-CDD47EAB858B}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {E6BC350B-0460-4831-B737-CDD47EAB858B}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {ADD48CEB-8938-4F9A-80D7-F48BE536C094}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 60 | {ADD48CEB-8938-4F9A-80D7-F48BE536C094}.Debug|Any CPU.Build.0 = Debug|Any CPU 61 | {ADD48CEB-8938-4F9A-80D7-F48BE536C094}.Release|Any CPU.ActiveCfg = Release|Any CPU 62 | {ADD48CEB-8938-4F9A-80D7-F48BE536C094}.Release|Any CPU.Build.0 = Release|Any CPU 63 | {0A04082B-C276-4A55-9E43-56D483AC0E7C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 64 | {0A04082B-C276-4A55-9E43-56D483AC0E7C}.Debug|Any CPU.Build.0 = Debug|Any CPU 65 | {0A04082B-C276-4A55-9E43-56D483AC0E7C}.Release|Any CPU.ActiveCfg = Release|Any CPU 66 | {0A04082B-C276-4A55-9E43-56D483AC0E7C}.Release|Any CPU.Build.0 = Release|Any CPU 67 | EndGlobalSection 68 | GlobalSection(SolutionProperties) = preSolution 69 | HideSolutionNode = FALSE 70 | EndGlobalSection 71 | GlobalSection(NestedProjects) = preSolution 72 | {D7F89B94-6694-4244-8028-85448F3D5DD9} = {4EEA2F94-3AA8-4D14-8A99-0BBD57DFE0B8} 73 | {31F15CEF-D08C-48FF-AE30-070F17FA77DB} = {D7F89B94-6694-4244-8028-85448F3D5DD9} 74 | {6B5FC3E5-596D-462E-8EA6-858503E76ABF} = {D7F89B94-6694-4244-8028-85448F3D5DD9} 75 | {AF06E745-6C9D-4595-9801-796D03B5DA91} = {4EEA2F94-3AA8-4D14-8A99-0BBD57DFE0B8} 76 | {FDA25617-E118-4B45-9A8A-ACCE518F2EA0} = {AF06E745-6C9D-4595-9801-796D03B5DA91} 77 | {6DD9178D-8470-4449-BF66-7D09DDB20A7A} = {4EEA2F94-3AA8-4D14-8A99-0BBD57DFE0B8} 78 | {75EBCB31-AC5C-4EBD-826E-5BA56758A7E8} = {6DD9178D-8470-4449-BF66-7D09DDB20A7A} 79 | {E6BC350B-0460-4831-B737-CDD47EAB858B} = {6DD9178D-8470-4449-BF66-7D09DDB20A7A} 80 | {ADD48CEB-8938-4F9A-80D7-F48BE536C094} = {6DD9178D-8470-4449-BF66-7D09DDB20A7A} 81 | {0A04082B-C276-4A55-9E43-56D483AC0E7C} = {6DD9178D-8470-4449-BF66-7D09DDB20A7A} 82 | EndGlobalSection 83 | GlobalSection(ExtensibilityGlobals) = postSolution 84 | SolutionGuid = {66E3BF6C-660B-4B62-BB1B-5871AADB12B3} 85 | EndGlobalSection 86 | EndGlobal 87 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.4' 2 | 3 | services: 4 | rabbitmq: 5 | image: rabbitmq:3-management-alpine 6 | ports: 7 | - "15672:15672" 8 | - "5672:5672" 9 | servicea: 10 | image: workshop/servicea:${TAG:-latest} 11 | build: 12 | context: . 13 | dockerfile: src/Services/Workshop.Microservices.ServiceA/Dockerfile 14 | ports: 15 | - "5001:5001" 16 | container_name: servicea 17 | depends_on: 18 | - rabbitmq 19 | 20 | serviceb: 21 | image: workshop/serviceb:${TAG:-latest} 22 | build: 23 | context: . 24 | dockerfile: src/Services/Workshop.Microservices.ServiceB/Dockerfile 25 | ports: 26 | - "5002:5002" 27 | container_name: serviceb 28 | depends_on: 29 | - rabbitmq 30 | 31 | ocelotapigateway: 32 | image: workshop/ocelotapigateway:${TAG:-latest} 33 | build: 34 | context: . 35 | dockerfile: src/ApiGateways/Workshop.Microservices.Ocelot.ApiGateway/Dockerfile 36 | ports: 37 | - "5000:5000" 38 | container_name: ocelotapigateway 39 | depends_on: 40 | - servicea 41 | - serviceb 42 | - rabbitmq -------------------------------------------------------------------------------- /src/ApiGateways/Workshop.Microservices.Ocelot.ApiGateway/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base 2 | WORKDIR /app 3 | EXPOSE 5000 4 | 5 | FROM microsoft/dotnet:2.1-sdk AS build 6 | WORKDIR /src 7 | COPY . . 8 | WORKDIR /src/src/ApiGateways/Workshop.Microservices.Ocelot.ApiGateway 9 | RUN dotnet restore 10 | RUN dotnet build --no-restore -c Release -o /app 11 | 12 | FROM build AS publish 13 | RUN dotnet publish --no-restore -c Release -o /app 14 | 15 | FROM base AS final 16 | WORKDIR /app 17 | COPY --from=publish /app . 18 | ENTRYPOINT ["dotnet", "Workshop.Microservices.Ocelot.ApiGateway.dll"] -------------------------------------------------------------------------------- /src/ApiGateways/Workshop.Microservices.Ocelot.ApiGateway/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Microsoft.AspNetCore; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.Extensions.Configuration; 5 | 6 | namespace Workshop.Microservices.Ocelot.ApiGateway 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | CreateWebHostBuilder(args).Build().Run(); 13 | } 14 | 15 | private static IWebHostBuilder CreateWebHostBuilder(string[] args) => 16 | WebHost.CreateDefaultBuilder(args) 17 | .ConfigureAppConfiguration(ic => ic.AddJsonFile("ocelot.json")) 18 | .UseContentRoot(Directory.GetCurrentDirectory()) 19 | .UseKestrel() 20 | .UseUrls("http://*:5000") 21 | .UseStartup(); 22 | } 23 | } -------------------------------------------------------------------------------- /src/ApiGateways/Workshop.Microservices.Ocelot.ApiGateway/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Ocelot.DependencyInjection; 6 | using Ocelot.Middleware; 7 | 8 | namespace Workshop.Microservices.Ocelot.ApiGateway 9 | { 10 | public class Startup 11 | { 12 | public Startup(IConfiguration configuration) 13 | { 14 | Configuration = configuration; 15 | } 16 | 17 | private IConfiguration Configuration { get; } 18 | 19 | public void ConfigureServices(IServiceCollection services) 20 | { 21 | services.AddMvc(); 22 | 23 | services.AddOcelot(Configuration); 24 | } 25 | 26 | public async void Configure(IApplicationBuilder app, IHostingEnvironment env) 27 | { 28 | app.UseMvc(); 29 | 30 | await app.UseOcelot(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/ApiGateways/Workshop.Microservices.Ocelot.ApiGateway/Workshop.Microservices.Ocelot.ApiGateway.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/ApiGateways/Workshop.Microservices.Ocelot.ApiGateway/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Warning" 5 | } 6 | }, 7 | "AllowedHosts": "*" 8 | } 9 | -------------------------------------------------------------------------------- /src/ApiGateways/Workshop.Microservices.Ocelot.ApiGateway/ocelot.json: -------------------------------------------------------------------------------- 1 | { 2 | "ReRoutes": [ 3 | { 4 | "DownstreamPathTemplate": "/api/service-a", 5 | "DownstreamScheme": "http", 6 | "DownstreamHostAndPorts": [ 7 | { 8 | "Host": "servicea", 9 | "Port": 5001 10 | } 11 | ], 12 | "UpstreamPathTemplate": "/api/a", 13 | "Key": "ServiceA" 14 | }, 15 | { 16 | "DownstreamPathTemplate": "/api/service-b", 17 | "DownstreamScheme": "http", 18 | "DownstreamHostAndPorts": [ 19 | { 20 | "Host": "serviceb", 21 | "Port": 5002 22 | } 23 | ], 24 | "UpstreamPathTemplate": "/api/b", 25 | "Key": "ServiceB" 26 | } 27 | ], 28 | "Aggregates": [ 29 | { 30 | "ReRouteKeys": [ 31 | "ServiceA", 32 | "ServiceB" 33 | ], 34 | "UpstreamPathTemplate": "/api/a-b" 35 | } 36 | ], 37 | "GlobalConfiguration": { 38 | "RequestIdKey": "OcRequestId" 39 | } 40 | } -------------------------------------------------------------------------------- /src/Infrastructure/Workshop.Microservices.Domain/Events/SendInformationToServiceBEvent.cs: -------------------------------------------------------------------------------- 1 | using Workshop.Microservices.EventBus.Events; 2 | 3 | namespace Workshop.Microservices.Domain.Events 4 | { 5 | public class SendInformationToServiceBEvent : IntegrationEvent 6 | { 7 | public string Information { get; set; } 8 | 9 | public SendInformationToServiceBEvent(string information) 10 | { 11 | Information = information; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Infrastructure/Workshop.Microservices.Domain/Workshop.Microservices.Domain.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/Infrastructure/Workshop.Microservices.EventBus.RabbitMq/DefaultRabbitMqPersistentConnection.cs: -------------------------------------------------------------------------------- 1 | using Polly; 2 | using RabbitMQ.Client; 3 | using RabbitMQ.Client.Events; 4 | using RabbitMQ.Client.Exceptions; 5 | using System; 6 | using System.Net.Sockets; 7 | 8 | namespace Workshop.Microservices.EventBus.RabbitMq 9 | { 10 | public class DefaultRabbitMqPersistentConnection : IRabbitMqPersistentConnection 11 | { 12 | private readonly IConnectionFactory _connectionFactory; 13 | private readonly int _retryCount; 14 | private IConnection _connection; 15 | private bool _disposed; 16 | 17 | readonly object _syncRoot = new object(); 18 | 19 | public DefaultRabbitMqPersistentConnection(IConnectionFactory connectionFactory, int retryCount = 5) 20 | { 21 | _connectionFactory = connectionFactory ?? throw new ArgumentNullException(nameof(connectionFactory)); 22 | _retryCount = retryCount; 23 | } 24 | 25 | public bool IsConnected => _connection != null && _connection.IsOpen && !_disposed; 26 | 27 | public IModel CreateModel() 28 | { 29 | if (!IsConnected) 30 | { 31 | throw new InvalidOperationException("No RabbitMq connections are available to perform this action"); 32 | } 33 | 34 | return _connection.CreateModel(); 35 | } 36 | 37 | public void Dispose() 38 | { 39 | if (_disposed) return; 40 | 41 | _disposed = true; 42 | 43 | _connection.Dispose(); 44 | } 45 | 46 | public bool TryConnect() 47 | { 48 | lock (_syncRoot) 49 | { 50 | var policy = Policy.Handle() 51 | .Or() 52 | .WaitAndRetry(_retryCount, 53 | retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), 54 | (ex, time) => { } 55 | ); 56 | 57 | policy.Execute(() => 58 | { 59 | _connection = _connectionFactory 60 | .CreateConnection(); 61 | }); 62 | 63 | if (!IsConnected) return false; 64 | 65 | _connection.ConnectionShutdown += OnConnectionShutdown; 66 | _connection.CallbackException += OnCallbackException; 67 | _connection.ConnectionBlocked += OnConnectionBlocked; 68 | 69 | return true; 70 | } 71 | } 72 | 73 | private void OnConnectionBlocked(object sender, ConnectionBlockedEventArgs e) 74 | { 75 | if (_disposed) return; 76 | 77 | TryConnect(); 78 | } 79 | 80 | private void OnCallbackException(object sender, CallbackExceptionEventArgs e) 81 | { 82 | if (_disposed) return; 83 | 84 | TryConnect(); 85 | } 86 | 87 | private void OnConnectionShutdown(object sender, ShutdownEventArgs reason) 88 | { 89 | if (_disposed) return; 90 | 91 | TryConnect(); 92 | } 93 | } 94 | } -------------------------------------------------------------------------------- /src/Infrastructure/Workshop.Microservices.EventBus.RabbitMq/EventBusRabbitMq.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Newtonsoft.Json; 3 | using Newtonsoft.Json.Linq; 4 | using Polly; 5 | using RabbitMQ.Client; 6 | using RabbitMQ.Client.Events; 7 | using RabbitMQ.Client.Exceptions; 8 | using System; 9 | using System.Net.Sockets; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | using Workshop.Microservices.EventBus.Abstractions; 13 | using Workshop.Microservices.EventBus.Events; 14 | 15 | namespace Workshop.Microservices.EventBus.RabbitMq 16 | { 17 | public class EventBusRabbitMq : IEventBus, IDisposable 18 | { 19 | private const string BrokerName = "workshop-microservices_event_bus"; 20 | private const string AutoFacScopeName = "workshop-microservices_event_bus"; 21 | 22 | private readonly IRabbitMqPersistentConnection _persistentConnection; 23 | private readonly IEventBusSubscriptionsManager _subsManager; 24 | private readonly ILifetimeScope _autoFac; 25 | private readonly int _retryCount; 26 | 27 | private IModel _consumerChannel; 28 | private readonly string _queueName; 29 | 30 | public EventBusRabbitMq(IRabbitMqPersistentConnection persistentConnection, 31 | ILifetimeScope autoFac, 32 | IEventBusSubscriptionsManager subsManager, 33 | string queueName = null, 34 | int retryCount = 5) 35 | { 36 | _persistentConnection = persistentConnection ?? throw new ArgumentNullException(nameof(persistentConnection)); 37 | _subsManager = subsManager ?? new InMemoryEventBusSubscriptionsManager(); 38 | _queueName = queueName; 39 | _consumerChannel = CreateConsumerChannel(); 40 | _autoFac = autoFac; 41 | _retryCount = retryCount; 42 | } 43 | 44 | public void Publish(IntegrationEvent @event) 45 | { 46 | if (!_persistentConnection.IsConnected) 47 | { 48 | _persistentConnection.TryConnect(); 49 | } 50 | 51 | var policy = Policy.Handle() 52 | .Or() 53 | .WaitAndRetry(_retryCount, retryAttempt => TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)), (ex, time) => { }); 54 | 55 | using (var channel = _persistentConnection.CreateModel()) 56 | { 57 | var eventName = @event.GetType() 58 | .Name; 59 | 60 | channel.ExchangeDeclare(exchange: BrokerName, 61 | type: "direct"); 62 | 63 | var message = JsonConvert.SerializeObject(@event); 64 | var body = Encoding.UTF8.GetBytes(message); 65 | 66 | policy.Execute(() => 67 | { 68 | var properties = channel.CreateBasicProperties(); 69 | properties.DeliveryMode = 2; 70 | 71 | channel.BasicPublish(exchange: BrokerName, 72 | routingKey: eventName, 73 | mandatory: true, 74 | basicProperties: properties, 75 | body: body); 76 | }); 77 | } 78 | } 79 | 80 | public void Subscribe() 81 | where T : IntegrationEvent 82 | where TH : IIntegrationEventHandler 83 | { 84 | var eventName = _subsManager.GetEventKey(); 85 | DoInternalSubscription(eventName); 86 | _subsManager.AddSubscription(); 87 | } 88 | 89 | private void DoInternalSubscription(string eventName) 90 | { 91 | var containsKey = _subsManager.HasSubscriptionsForEvent(eventName); 92 | if (!containsKey) 93 | { 94 | if (!_persistentConnection.IsConnected) 95 | { 96 | _persistentConnection.TryConnect(); 97 | } 98 | 99 | using (var channel = _persistentConnection.CreateModel()) 100 | { 101 | channel.QueueBind(queue: _queueName, 102 | exchange: BrokerName, 103 | routingKey: eventName); 104 | } 105 | } 106 | } 107 | 108 | public void Dispose() 109 | { 110 | _consumerChannel?.Dispose(); 111 | 112 | _subsManager.Clear(); 113 | } 114 | 115 | private IModel CreateConsumerChannel() 116 | { 117 | if (!_persistentConnection.IsConnected) 118 | { 119 | _persistentConnection.TryConnect(); 120 | } 121 | 122 | var channel = _persistentConnection.CreateModel(); 123 | 124 | channel.ExchangeDeclare(exchange: BrokerName, 125 | type: "direct"); 126 | 127 | channel.QueueDeclare(queue: _queueName, 128 | durable: true, 129 | exclusive: false, 130 | autoDelete: false, 131 | arguments: null); 132 | 133 | 134 | var consumer = new EventingBasicConsumer(channel); 135 | consumer.Received += async (model, ea) => 136 | { 137 | var eventName = ea.RoutingKey; 138 | var message = Encoding.UTF8.GetString(ea.Body); 139 | 140 | await ProcessEvent(eventName, message); 141 | 142 | channel.BasicAck(ea.DeliveryTag, multiple: false); 143 | }; 144 | 145 | channel.BasicConsume(queue: _queueName, 146 | autoAck: false, 147 | consumer: consumer); 148 | 149 | channel.CallbackException += (sender, ea) => 150 | { 151 | _consumerChannel.Dispose(); 152 | _consumerChannel = CreateConsumerChannel(); 153 | }; 154 | 155 | return channel; 156 | } 157 | 158 | private async Task ProcessEvent(string eventName, string message) 159 | { 160 | if (_subsManager.HasSubscriptionsForEvent(eventName)) 161 | { 162 | using (var scope = _autoFac.BeginLifetimeScope(AutoFacScopeName)) 163 | { 164 | var subscriptions = _subsManager.GetHandlersForEvent(eventName); 165 | foreach (var subscription in subscriptions) 166 | { 167 | if (subscription.IsDynamic) 168 | { 169 | var handler = scope.ResolveOptional(subscription.HandlerType) as IDynamicIntegrationEventHandler; 170 | dynamic eventData = JObject.Parse(message); 171 | await handler.Handle(eventData); 172 | } 173 | else 174 | { 175 | var eventType = _subsManager.GetEventTypeByName(eventName); 176 | var integrationEvent = JsonConvert.DeserializeObject(message, eventType); 177 | var handler = scope.ResolveOptional(subscription.HandlerType); 178 | var concreteType = typeof(IIntegrationEventHandler<>).MakeGenericType(eventType); 179 | await (Task) concreteType.GetMethod("Handle").Invoke(handler, new[] { integrationEvent }); 180 | } 181 | } 182 | } 183 | } 184 | } 185 | } 186 | } -------------------------------------------------------------------------------- /src/Infrastructure/Workshop.Microservices.EventBus.RabbitMq/IRabbitMqPersistentConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RabbitMQ.Client; 3 | 4 | namespace Workshop.Microservices.EventBus.RabbitMq 5 | { 6 | public interface IRabbitMqPersistentConnection : IDisposable 7 | { 8 | bool IsConnected { get; } 9 | 10 | bool TryConnect(); 11 | 12 | IModel CreateModel(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Infrastructure/Workshop.Microservices.EventBus.RabbitMq/Workshop.Microservices.EventBus.RabbitMq.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/Infrastructure/Workshop.Microservices.EventBus/Abstractions/IDynamicIntegrationEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace Workshop.Microservices.EventBus.Abstractions 4 | { 5 | public interface IDynamicIntegrationEventHandler 6 | { 7 | Task Handle(dynamic eventData); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Infrastructure/Workshop.Microservices.EventBus/Abstractions/IEventBus.cs: -------------------------------------------------------------------------------- 1 | using Workshop.Microservices.EventBus.Events; 2 | 3 | namespace Workshop.Microservices.EventBus.Abstractions 4 | { 5 | public interface IEventBus 6 | { 7 | void Publish(IntegrationEvent @event); 8 | 9 | void Subscribe() where T : IntegrationEvent where TH : IIntegrationEventHandler; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Infrastructure/Workshop.Microservices.EventBus/Abstractions/IIntegrationEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Workshop.Microservices.EventBus.Events; 3 | 4 | namespace Workshop.Microservices.EventBus.Abstractions 5 | { 6 | public interface IIntegrationEventHandler where TIntegrationEvent : IntegrationEvent 7 | { 8 | Task Handle(TIntegrationEvent @event); 9 | } 10 | } -------------------------------------------------------------------------------- /src/Infrastructure/Workshop.Microservices.EventBus/Events/IntegrationEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Workshop.Microservices.EventBus.Events 4 | { 5 | public class IntegrationEvent 6 | { 7 | public IntegrationEvent() 8 | { 9 | Id = Guid.NewGuid(); 10 | CreationDate = DateTime.UtcNow; 11 | } 12 | 13 | public Guid Id { get; } 14 | public DateTime CreationDate { get; } 15 | } 16 | } -------------------------------------------------------------------------------- /src/Infrastructure/Workshop.Microservices.EventBus/IEventBusSubscriptionsManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Workshop.Microservices.EventBus.Abstractions; 4 | using Workshop.Microservices.EventBus.Events; 5 | 6 | namespace Workshop.Microservices.EventBus 7 | { 8 | public interface IEventBusSubscriptionsManager 9 | { 10 | void AddSubscription() 11 | where T : IntegrationEvent 12 | where TH : IIntegrationEventHandler; 13 | 14 | bool HasSubscriptionsForEvent(string eventName); 15 | 16 | Type GetEventTypeByName(string eventName); 17 | 18 | void Clear(); 19 | 20 | IEnumerable GetHandlersForEvent(string eventName); 21 | 22 | string GetEventKey(); 23 | } 24 | } -------------------------------------------------------------------------------- /src/Infrastructure/Workshop.Microservices.EventBus/InMemoryEventBusSubscriptionsManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Workshop.Microservices.EventBus.Abstractions; 5 | using Workshop.Microservices.EventBus.Events; 6 | 7 | namespace Workshop.Microservices.EventBus 8 | { 9 | public class InMemoryEventBusSubscriptionsManager : IEventBusSubscriptionsManager 10 | { 11 | private readonly Dictionary> _handlers; 12 | private readonly List _eventTypes; 13 | 14 | public InMemoryEventBusSubscriptionsManager() 15 | { 16 | _handlers = new Dictionary>(); 17 | _eventTypes = new List(); 18 | } 19 | 20 | public void Clear() => _handlers.Clear(); 21 | 22 | public void AddSubscription() 23 | where T : IntegrationEvent 24 | where TH : IIntegrationEventHandler 25 | { 26 | var eventName = GetEventKey(); 27 | DoAddSubscription(typeof(TH), eventName, isDynamic: false); 28 | _eventTypes.Add(typeof(T)); 29 | } 30 | 31 | private void DoAddSubscription(Type handlerType, string eventName, bool isDynamic) 32 | { 33 | if (!HasSubscriptionsForEvent(eventName)) 34 | { 35 | _handlers.Add(eventName, new List()); 36 | } 37 | 38 | if (_handlers[eventName].Any(s => s.HandlerType == handlerType)) 39 | { 40 | throw new ArgumentException( 41 | $"Handler Type {handlerType.Name} already registered for '{eventName}'", 42 | nameof(handlerType)); 43 | } 44 | 45 | _handlers[eventName] 46 | .Add(isDynamic 47 | ? SubscriptionInfo.Dynamic(handlerType) 48 | : SubscriptionInfo.Typed(handlerType)); 49 | } 50 | 51 | public IEnumerable GetHandlersForEvent(string eventName) => _handlers[eventName]; 52 | 53 | public bool HasSubscriptionsForEvent(string eventName) => _handlers.ContainsKey(eventName); 54 | 55 | public Type GetEventTypeByName(string eventName) => _eventTypes.SingleOrDefault(t => t.Name == eventName); 56 | 57 | public string GetEventKey() 58 | { 59 | return typeof(T).Name; 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /src/Infrastructure/Workshop.Microservices.EventBus/SubscriptionInfo.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Workshop.Microservices.EventBus 4 | { 5 | public class SubscriptionInfo 6 | { 7 | public bool IsDynamic { get; } 8 | 9 | public Type HandlerType { get; } 10 | 11 | private SubscriptionInfo(bool isDynamic, Type handlerType) 12 | { 13 | IsDynamic = isDynamic; 14 | HandlerType = handlerType; 15 | } 16 | 17 | public static SubscriptionInfo Dynamic(Type handlerType) 18 | { 19 | return new SubscriptionInfo(true, handlerType); 20 | } 21 | 22 | public static SubscriptionInfo Typed(Type handlerType) 23 | { 24 | return new SubscriptionInfo(false, handlerType); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Infrastructure/Workshop.Microservices.EventBus/Workshop.Microservices.EventBus.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/Infrastructure/Workshop.Microservices.Extensions/AutoFacExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Autofac; 3 | using Autofac.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace Workshop.Microservices.Extensions 7 | { 8 | public static class AutoFacExtensions 9 | { 10 | public static IServiceProvider BuildWithAutoFac(this IServiceCollection services) 11 | { 12 | var container = new ContainerBuilder(); 13 | container.Populate(services); 14 | 15 | return new AutofacServiceProvider(container.Build()); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Infrastructure/Workshop.Microservices.Extensions/EventBusExtensions.cs: -------------------------------------------------------------------------------- 1 | using Autofac; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Workshop.Microservices.EventBus; 5 | using Workshop.Microservices.EventBus.Abstractions; 6 | using Workshop.Microservices.EventBus.RabbitMq; 7 | 8 | namespace Workshop.Microservices.Extensions 9 | { 10 | public static class EventBusExtensions 11 | { 12 | public static void RegisterEventBus(this IServiceCollection services, IConfiguration configuration) 13 | { 14 | var subscriptionClientName = configuration["SubscriptionClientName"]; 15 | 16 | services.AddSingleton(sp => 17 | { 18 | var rabbitMqPersistentConnection = sp.GetRequiredService(); 19 | var iLifetimeScope = sp.GetRequiredService(); 20 | var eventBusSubscriptionsManager = sp.GetRequiredService(); 21 | 22 | var retryCount = 5; 23 | if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) 24 | { 25 | retryCount = int.Parse(configuration["EventBusRetryCount"]); 26 | } 27 | 28 | return new EventBusRabbitMq(rabbitMqPersistentConnection, 29 | iLifetimeScope, 30 | eventBusSubscriptionsManager, 31 | subscriptionClientName, 32 | retryCount); 33 | }); 34 | 35 | services.AddSingleton(); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /src/Infrastructure/Workshop.Microservices.Extensions/RabbitMqExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using RabbitMQ.Client; 4 | using Workshop.Microservices.EventBus.RabbitMq; 5 | 6 | namespace Workshop.Microservices.Extensions 7 | { 8 | public static class RabbitMqExtensions 9 | { 10 | public static void RegisterRabbitMq(this IServiceCollection services, IConfiguration configuration) 11 | { 12 | services.AddSingleton(sp => 13 | { 14 | var factory = new ConnectionFactory 15 | { 16 | HostName = configuration["EventBusConnection"] 17 | }; 18 | 19 | if (!string.IsNullOrEmpty(configuration["EventBusUserName"])) 20 | { 21 | factory.UserName = configuration["EventBusUserName"]; 22 | } 23 | 24 | if (!string.IsNullOrEmpty(configuration["EventBusPassword"])) 25 | { 26 | factory.Password = configuration["EventBusPassword"]; 27 | } 28 | 29 | var retryCount = 5; 30 | if (!string.IsNullOrEmpty(configuration["EventBusRetryCount"])) 31 | { 32 | retryCount = int.Parse(configuration["EventBusRetryCount"]); 33 | } 34 | 35 | return new DefaultRabbitMqPersistentConnection(factory, retryCount); 36 | }); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/Infrastructure/Workshop.Microservices.Extensions/Workshop.Microservices.Extensions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/Services/Workshop.Microservices.ServiceA/CommandHandlers/ACommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MediatR; 5 | using Workshop.Microservices.ServiceA.Commands; 6 | 7 | namespace Workshop.Microservices.ServiceA.CommandHandlers 8 | { 9 | public class ACommandHandler : IRequestHandler 10 | { 11 | public async Task Handle(ACommand request, CancellationToken cancellationToken) 12 | { 13 | Console.WriteLine($"ACommandHandler Information: {request.Information} "); 14 | 15 | return true; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Services/Workshop.Microservices.ServiceA/Commands/ACommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace Workshop.Microservices.ServiceA.Commands 4 | { 5 | public class ACommand : IRequest 6 | { 7 | public string Information { get; private set; } 8 | 9 | public ACommand(string information) 10 | { 11 | Information = information; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/Services/Workshop.Microservices.ServiceA/Controllers/ServiceAController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using MediatR; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Workshop.Microservices.Domain.Events; 5 | using Workshop.Microservices.EventBus.Abstractions; 6 | using Workshop.Microservices.ServiceA.Commands; 7 | using Workshop.Microservices.ServiceA.Events; 8 | 9 | namespace Workshop.Microservices.ServiceA.Controllers 10 | { 11 | [Route("api/service-a")] 12 | [ApiController] 13 | public class ServiceAController : ControllerBase 14 | { 15 | private readonly IEventBus _eventBus; 16 | private readonly IMediator _mediator; 17 | 18 | public ServiceAController(IEventBus eventBus, IMediator mediator) 19 | { 20 | _eventBus = eventBus; 21 | _mediator = mediator; 22 | } 23 | 24 | [HttpGet] 25 | public async Task Get() 26 | { 27 | await _mediator.Publish(new AEvent("sending notification from service A")); 28 | 29 | await _mediator.Send(new ACommand("sending command from service A")); 30 | 31 | _eventBus.Publish(new SendInformationToServiceBEvent("sending event by event bus from service A")); 32 | 33 | return Ok(new 34 | { 35 | message = "returning from service A" 36 | }); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/Services/Workshop.Microservices.ServiceA/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base 2 | WORKDIR /app 3 | EXPOSE 5001 4 | 5 | FROM microsoft/dotnet:2.1-sdk AS build 6 | WORKDIR /src 7 | COPY . . 8 | WORKDIR /src/src/Services/Workshop.Microservices.ServiceA 9 | RUN dotnet restore 10 | RUN dotnet build --no-restore -c Release -o /app 11 | 12 | FROM build AS publish 13 | RUN dotnet publish --no-restore -c Release -o /app 14 | 15 | FROM base AS final 16 | WORKDIR /app 17 | COPY --from=publish /app . 18 | ENTRYPOINT ["dotnet", "Workshop.Microservices.ServiceA.dll"] -------------------------------------------------------------------------------- /src/Services/Workshop.Microservices.ServiceA/Events/AEvent.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace Workshop.Microservices.ServiceA.Events 4 | { 5 | public class AEvent : INotification 6 | { 7 | public string Information { get; private set; } 8 | 9 | public AEvent(string information) 10 | { 11 | Information = information; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Services/Workshop.Microservices.ServiceA/NotificationHandlers/AEventNotificationHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MediatR; 5 | using Workshop.Microservices.ServiceA.Events; 6 | 7 | namespace Workshop.Microservices.ServiceA.NotificationHandlers 8 | { 9 | public class AEventNotificationHandler : INotificationHandler 10 | { 11 | public Task Handle(AEvent notification, CancellationToken cancellationToken) 12 | { 13 | Console.WriteLine($"AEventNotificationHandler Information: {notification.Information} "); 14 | 15 | return Task.FromResult(0); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Services/Workshop.Microservices.ServiceA/NotificationHandlers/AnotherAEventNotificationHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MediatR; 5 | using Workshop.Microservices.ServiceA.Events; 6 | 7 | namespace Workshop.Microservices.ServiceA.NotificationHandlers 8 | { 9 | public class AnotherAEventNotificationHandler : INotificationHandler 10 | { 11 | public Task Handle(AEvent notification, CancellationToken cancellationToken) 12 | { 13 | Console.WriteLine($"AnotherAEventNotificationHandler Information: {notification.Information} "); 14 | 15 | return Task.FromResult(0); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Services/Workshop.Microservices.ServiceA/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Microsoft.AspNetCore; 3 | using Microsoft.AspNetCore.Hosting; 4 | 5 | namespace Workshop.Microservices.ServiceA 6 | { 7 | public class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | CreateWebHostBuilder(args).Build().Run(); 12 | } 13 | 14 | private static IWebHostBuilder CreateWebHostBuilder(string[] args) => 15 | WebHost.CreateDefaultBuilder(args) 16 | .UseContentRoot(Directory.GetCurrentDirectory()) 17 | .UseKestrel() 18 | .UseUrls("http://*:5001") 19 | .UseStartup(); 20 | } 21 | } -------------------------------------------------------------------------------- /src/Services/Workshop.Microservices.ServiceA/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using MediatR; 4 | using Microsoft.AspNetCore.Builder; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Workshop.Microservices.Extensions; 9 | 10 | namespace Workshop.Microservices.ServiceA 11 | { 12 | public class Startup 13 | { 14 | public Startup(IConfiguration configuration) 15 | { 16 | Configuration = configuration; 17 | } 18 | 19 | private IConfiguration Configuration { get; } 20 | 21 | public IServiceProvider ConfigureServices(IServiceCollection services) 22 | { 23 | services.AddMvc(); 24 | services.RegisterRabbitMq(Configuration); 25 | services.RegisterEventBus(Configuration); 26 | services.AddMediatR(typeof(Startup).GetTypeInfo().Assembly); 27 | 28 | return services.BuildWithAutoFac(); 29 | } 30 | 31 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 32 | { 33 | app.UseMvc(); 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /src/Services/Workshop.Microservices.ServiceA/Workshop.Microservices.ServiceA.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/Services/Workshop.Microservices.ServiceA/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "SubscriptionClientName": "ServiceA", 3 | "EventBusConnection": "127.0.0.1", 4 | "EventBusUserName": "guest", 5 | "EventBusPassword": "guest", 6 | "EventBusRetryCount": 5 7 | } -------------------------------------------------------------------------------- /src/Services/Workshop.Microservices.ServiceB/Controllers/ServiceBController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | namespace Workshop.Microservices.ServiceB.Controllers 4 | { 5 | [Route("api/service-b")] 6 | [ApiController] 7 | public class ServiceBController : ControllerBase 8 | { 9 | [HttpGet] 10 | public IActionResult Get() 11 | { 12 | return Ok(new 13 | { 14 | message = "returning from service B" 15 | }); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/Services/Workshop.Microservices.ServiceB/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base 2 | WORKDIR /app 3 | EXPOSE 5002 4 | 5 | FROM microsoft/dotnet:2.1-sdk AS build 6 | WORKDIR /src 7 | COPY . . 8 | WORKDIR /src/src/Services/Workshop.Microservices.ServiceB 9 | RUN dotnet restore 10 | RUN dotnet build --no-restore -c Release -o /app 11 | 12 | FROM build AS publish 13 | RUN dotnet publish --no-restore -c Release -o /app 14 | 15 | FROM base AS final 16 | WORKDIR /app 17 | COPY --from=publish /app . 18 | ENTRYPOINT ["dotnet", "Workshop.Microservices.ServiceB.dll"] -------------------------------------------------------------------------------- /src/Services/Workshop.Microservices.ServiceB/IntegrationEvents/EventHandling/SendInformationToServiceBEventHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using Workshop.Microservices.Domain.Events; 4 | using Workshop.Microservices.EventBus.Abstractions; 5 | 6 | namespace Workshop.Microservices.ServiceB.IntegrationEvents.EventHandling 7 | { 8 | public class SendInformationToServiceBEventHandler : IIntegrationEventHandler 9 | { 10 | public async Task Handle(SendInformationToServiceBEvent @event) 11 | { 12 | Console.WriteLine($"Event Id: {@event.Id} Creation Date: {@event.CreationDate} Message: {@event.Information}"); 13 | 14 | await Task.CompletedTask; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/Services/Workshop.Microservices.ServiceB/Program.cs: -------------------------------------------------------------------------------- 1 | using System.IO; 2 | using Microsoft.AspNetCore; 3 | using Microsoft.AspNetCore.Hosting; 4 | 5 | namespace Workshop.Microservices.ServiceB 6 | { 7 | public class Program 8 | { 9 | public static void Main(string[] args) 10 | { 11 | CreateWebHostBuilder(args).Build().Run(); 12 | } 13 | 14 | private static IWebHostBuilder CreateWebHostBuilder(string[] args) => 15 | WebHost.CreateDefaultBuilder(args) 16 | .UseContentRoot(Directory.GetCurrentDirectory()) 17 | .UseKestrel() 18 | .UseUrls("http://*:5002") 19 | .UseStartup(); 20 | } 21 | } -------------------------------------------------------------------------------- /src/Services/Workshop.Microservices.ServiceB/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | using Workshop.Microservices.Domain.Events; 7 | using Workshop.Microservices.EventBus.Abstractions; 8 | using Workshop.Microservices.Extensions; 9 | using Workshop.Microservices.ServiceB.IntegrationEvents.EventHandling; 10 | 11 | namespace Workshop.Microservices.ServiceB 12 | { 13 | public class Startup 14 | { 15 | public Startup(IConfiguration configuration) 16 | { 17 | Configuration = configuration; 18 | } 19 | 20 | private IConfiguration Configuration { get; } 21 | 22 | public IServiceProvider ConfigureServices(IServiceCollection services) 23 | { 24 | services.AddMvc(); 25 | services.RegisterRabbitMq(Configuration); 26 | services.RegisterEventBus(Configuration); 27 | 28 | services.AddSingleton(); 29 | 30 | return services.BuildWithAutoFac(); 31 | } 32 | 33 | public void Configure(IApplicationBuilder app, IHostingEnvironment env) 34 | { 35 | app.UseMvc(); 36 | 37 | var eventBus = app.ApplicationServices.GetRequiredService(); 38 | 39 | eventBus.Subscribe(); 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /src/Services/Workshop.Microservices.ServiceB/Workshop.Microservices.ServiceB.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/Services/Workshop.Microservices.ServiceB/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "SubscriptionClientName": "ServiceB", 3 | "EventBusConnection": "127.0.0.1", 4 | "EventBusUserName": "guest", 5 | "EventBusPassword": "guest", 6 | "EventBusRetryCount": 5 7 | } --------------------------------------------------------------------------------