Repository: suadev/dotnet-istanbul-microservices-demo Branch: master Commit: 257a6ea78dbf Files: 147 Total size: 142.5 KB Directory structure: gitextract_r_shzeh4/ ├── .github/ │ └── workflows/ │ └── dotnet-core.yml ├── .gitignore ├── .postman_project/ │ └── Dotnet_Istanbul.postman_collection.json ├── .vscode/ │ ├── launch.json │ └── tasks.json ├── DotnetIst.Api/ │ ├── README.MD │ └── src/ │ └── Api/ │ ├── Api.csproj │ ├── Authentication/ │ │ ├── AuthenticationExtensions.cs │ │ ├── AuthenticationService.cs │ │ ├── IAuthenticationService.cs │ │ └── Model/ │ │ ├── LoginModel.cs │ │ └── TokenModel.cs │ ├── Commands/ │ │ ├── Baskets/ │ │ │ └── AddProductToBasket.cs │ │ ├── Customers/ │ │ │ └── CreateCustomer.cs │ │ └── Orders/ │ │ └── CreateOrder.cs │ ├── Controllers/ │ │ ├── BaseController.cs │ │ ├── BasketController.cs │ │ ├── CustomerController.cs │ │ ├── HomeController.cs │ │ ├── OrderController.cs │ │ ├── ProductController.cs │ │ └── TokenController.cs │ ├── HttpServices/ │ │ ├── CustomerHttpService.cs │ │ ├── ICustomerHttpService.cs │ │ ├── IProductHttpService.cs │ │ └── ProductHttpService.cs │ ├── Models/ │ │ └── Customer.cs │ ├── Program.cs │ ├── Properties/ │ │ └── launchSettings.json │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── DotnetIst.Services.Customers/ │ ├── README.MD │ └── src/ │ └── Services.Customers/ │ ├── Commands/ │ │ ├── AddProductToBasket.cs │ │ └── CreateCustomer.cs │ ├── Controllers/ │ │ ├── BasketController.cs │ │ ├── CustomerController.cs │ │ └── HomeController.cs │ ├── Data/ │ │ ├── CustomerDBContext.cs │ │ ├── Entity/ │ │ │ ├── Basket.cs │ │ │ ├── BasketItem.cs │ │ │ └── Customer.cs │ │ ├── Migrations/ │ │ │ ├── 20190308211118_initial.Designer.cs │ │ │ ├── 20190308211118_initial.cs │ │ │ ├── 20190308222102_base_entity.Designer.cs │ │ │ ├── 20190308222102_base_entity.cs │ │ │ └── CustomerDBContextModelSnapshot.cs │ │ └── SeedData.cs │ ├── Events/ │ │ ├── OrderCompleted.cs │ │ └── ProductAddedToBasket.cs │ ├── Handlers/ │ │ ├── AddProductToBasketHandler.cs │ │ ├── CreateCustomerdHandler.cs │ │ └── OrderCompletedHandler.cs │ ├── HttpServices/ │ │ ├── IProductHttpService.cs │ │ └── ProductHttpService.cs │ ├── Models/ │ │ └── Product.cs │ ├── Program.cs │ ├── Properties/ │ │ └── launchSettings.json │ ├── Services.Customers.csproj │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── DotnetIst.Services.Notifications/ │ ├── README.MD │ └── src/ │ └── Services.Notifications/ │ ├── Controllers/ │ │ └── HomeController.cs │ ├── Events/ │ │ ├── OrderCompleted.cs │ │ └── OrderFailed.cs │ ├── Handlers/ │ │ ├── OrderCompletedHandler.cs │ │ └── OrderFailedHandler.cs │ ├── Program.cs │ ├── Properties/ │ │ └── launchSettings.json │ ├── Services.Notifications.csproj │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── DotnetIst.Services.Orders/ │ ├── README.md │ └── src/ │ └── Services.Orders/ │ ├── Commands/ │ │ └── CreateOrder.cs │ ├── Controllers/ │ │ └── HomeController.cs │ ├── Data/ │ │ ├── Entity/ │ │ │ ├── Order.cs │ │ │ ├── OrderItem.cs │ │ │ └── OrderStatus.cs │ │ ├── Migrations/ │ │ │ ├── 20190308211841_initial.Designer.cs │ │ │ ├── 20190308211841_initial.cs │ │ │ ├── 20190308222202_base_entity.Designer.cs │ │ │ ├── 20190308222202_base_entity.cs │ │ │ └── OrderDBContextModelSnapshot.cs │ │ ├── OrderDBContext.cs │ │ └── SeedData.cs │ ├── Events/ │ │ ├── OrderCompleted.cs │ │ ├── OrderCreated.cs │ │ ├── OrderFailed.cs │ │ ├── ProductsReserveFailed.cs │ │ └── ProductsReserved.cs │ ├── Handlers/ │ │ ├── CreateOrderHandler.cs │ │ ├── ProductsReserveFailedHandler.cs │ │ └── ProductsReservedHandler.cs │ ├── HttpServices/ │ │ ├── CustomerHttpService.cs │ │ └── ICustomerHttpService.cs │ ├── Models/ │ │ ├── Basket.cs │ │ └── BasketItem.cs │ ├── Program.cs │ ├── Properties/ │ │ └── launchSettings.json │ ├── Services.Orders.csproj │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── DotnetIst.Services.Products/ │ ├── README.md │ └── src/ │ └── Services.Products/ │ ├── Controllers/ │ │ ├── HomeController.cs │ │ └── ProductController.cs │ ├── Data/ │ │ ├── Entity/ │ │ │ └── Product.cs │ │ ├── Migrations/ │ │ │ ├── 20190308212022_initial.Designer.cs │ │ │ ├── 20190308212022_initial.cs │ │ │ ├── 20190308222331_base_entity.Designer.cs │ │ │ ├── 20190308222331_base_entity.cs │ │ │ └── ProductDBContextModelSnapshot.cs │ │ ├── ProductDBContext.cs │ │ └── SeedData.cs │ ├── Events/ │ │ ├── OrderCreated.cs │ │ ├── ProductsReserveFailed.cs │ │ └── ProductsReserved.cs │ ├── Handlers/ │ │ └── OrderCreatedHandler.cs │ ├── Program.cs │ ├── Properties/ │ │ └── launchSettings.json │ ├── Services.Products.csproj │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json ├── DotnetIst.Shared/ │ └── src/ │ └── Shared/ │ ├── Extensions.cs │ ├── Logging/ │ │ └── LoggingExtensions.cs │ ├── MessageHandlers/ │ │ ├── ICommandHandler.cs │ │ └── IEventHandler.cs │ ├── Messages/ │ │ ├── ICommand.cs │ │ ├── IEvent.cs │ │ └── MessageNamespaceAttribute.cs │ ├── Models/ │ │ ├── BaseEntity.cs │ │ └── HttpServiceOptions.cs │ ├── RabbitMq/ │ │ ├── BusPublisher.cs │ │ ├── BusSubscriber.cs │ │ ├── CorrelationContext.cs │ │ ├── IBusPublisher.cs │ │ ├── IBusSubscriber.cs │ │ ├── ICorrelationContext.cs │ │ ├── RabbitMqExtensions.cs │ │ └── RabbitMqOptions.cs │ └── Shared.csproj ├── README.md ├── docker-compose.yml └── dotnet-istanbul-microservices-demo.sln ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/dotnet-core.yml ================================================ name: .NET Core on: push: branches: [ master ] pull_request: branches: [ master ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Setup .NET Core uses: actions/setup-dotnet@v1 with: dotnet-version: 3.1.301 - name: Install dependencies run: dotnet restore - name: Build run: dotnet build --configuration Release --no-restore - name: Test run: dotnet test --no-restore --verbosity normal ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ # Visual Studio 2015 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # DNX project.lock.json project.fragment.lock.json artifacts/ *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding add-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # NuGet v3's project.json files produces more ignoreable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings node_modules/ orleans.codegen.cs # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # JetBrains Rider .idea/ *.sln.iml # CodeRush .cr/ # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json NuGet.config appsettings.local.json ================================================ FILE: .postman_project/Dotnet_Istanbul.postman_collection.json ================================================ { "info": { "_postman_id": "1ce3bc02-2070-441c-9d1a-386db626d78b", "name": "Dotnet_Istanbul", "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" }, "item": [ { "name": "0-RegisterCustomer", "request": { "method": "POST", "header": [ { "key": "Content-Type", "name": "Content-Type", "type": "text", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\"FirstName\" :\"Suat\",\n\t\"LastName\" :\"Köse\",\n\t\"Address\" :\"Üsküdar\",\n\t\"Email\" :\"suadev@gmail.com\",\n\t\"Password\": \"12345\"\n}" }, "url": { "raw": "http://localhost:5000/api/customer", "protocol": "http", "host": [ "localhost" ], "port": "5000", "path": [ "api", "customer" ] } }, "response": [] }, { "name": "1-GetToken", "request": { "method": "POST", "header": [ { "key": "Content-Type", "name": "Content-Type", "type": "text", "value": "application/json" } ], "body": { "mode": "raw", "raw": "{\n\t\"Email\" :\"suadev@gmail.com\",\n\t\"Password\" :\"12345\"\n}" }, "url": { "raw": "http://localhost:5000/api/token", "protocol": "http", "host": [ "localhost" ], "port": "5000", "path": [ "api", "token" ] } }, "response": [] }, { "name": "2-AddToBasket-Success", "request": { "method": "POST", "header": [ { "key": "Content-Type", "name": "Content-Type", "value": "application/json", "type": "text" }, { "key": "Authorization", "value": "bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InN1YWRldkBnbWFpbC5jb20iLCJDdXN0b21lcklkIjoiMDQ1NzFlNDQtMDJjYi00Mzc1LWE0MTMtN2NiYTMxZDk2NGMyIiwibmJmIjoxNTUzNjMxNDk5LCJleHAiOjE1NTM3MTc4OTksImlhdCI6MTU1MzYzMTQ5OSwiaXNzIjoid2l6bG8uYXBpLmRlbW8iLCJhdWQiOiJTb21lQ3VzdG9tQXBwIn0.cQOe_IVK8oYzjiDdaNOMo5aviiDMt_gX-PYL-n_NPjc", "type": "text" } ], "body": { "mode": "raw", "raw": "{\n\t\"ProductId\" :\"dc87e9ce-6d8d-4a01-a62d-2c2326472811\",\n\t\"Quantity\": 10\n}" }, "url": { "raw": "http://localhost:5000/api/basket", "protocol": "http", "host": [ "localhost" ], "port": "5000", "path": [ "api", "basket" ] } }, "response": [] }, { "name": "3-CreateOrder", "request": { "method": "POST", "header": [ { "key": "Content-Type", "name": "Content-Type", "value": "application/json", "type": "text" }, { "key": "Authorization", "value": "bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1bmlxdWVfbmFtZSI6InN1YWRldkBnbWFpbC5jb20iLCJDdXN0b21lcklkIjoiMDQ1NzFlNDQtMDJjYi00Mzc1LWE0MTMtN2NiYTMxZDk2NGMyIiwibmJmIjoxNTUzNjMxNDk5LCJleHAiOjE1NTM3MTc4OTksImlhdCI6MTU1MzYzMTQ5OSwiaXNzIjoid2l6bG8uYXBpLmRlbW8iLCJhdWQiOiJTb21lQ3VzdG9tQXBwIn0.cQOe_IVK8oYzjiDdaNOMo5aviiDMt_gX-PYL-n_NPjc", "type": "text" } ], "body": { "mode": "raw", "raw": "{}" }, "url": { "raw": "http://localhost:5000/api/order", "protocol": "http", "host": [ "localhost" ], "port": "5000", "path": [ "api", "order" ], "query": [ { "key": "", "value": "" } ] } }, "response": [] }, { "name": "GetAllProducts", "request": { "method": "GET", "header": [], "body": { "mode": "raw", "raw": "" }, "url": { "raw": "http://localhost:5000/api/product", "protocol": "http", "host": [ "localhost" ], "port": "5000", "path": [ "api", "product" ] } }, "response": [] } ] } ================================================ FILE: .vscode/launch.json ================================================ { // Use IntelliSense to learn about possible attributes. // Hover to view descriptions of existing attributes. // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "name": "api", "type": "coreclr", "request": "launch", "preLaunchTask": "build-api", "program": "${workspaceFolder}/DotnetIst.Api/src/Api/bin/Debug/netcoreapp2.2/Api.dll", "args": [], "cwd": "${workspaceFolder}/DotnetIst.Api/src/Api", "stopAtEntry": false, "internalConsoleOptions": "openOnSessionStart", "launchBrowser": { "enabled": true, "args": "${auto-detect-url}", "windows": { "command": "cmd.exe", "args": "/C start ${auto-detect-url}" }, "osx": { "command": "open" }, "linux": { "command": "xdg-open" } }, "env": { "ASPNETCORE_ENVIRONMENT": "Development", "ASPNETCORE_URLS": "http://localhost:5000" }, "sourceFileMap": { "/Views": "${workspaceFolder}/Views" } }, { "name": "customer", "type": "coreclr", "request": "launch", "preLaunchTask": "build-customer", "program": "${workspaceFolder}/DotnetIst.Services.Customers/src/Services.Customers/bin/Debug/netcoreapp2.2/Services.Customers.dll", "args": [], "cwd": "${workspaceFolder}/DotnetIst.Services.Customers/src/Services.Customers", "stopAtEntry": false, "internalConsoleOptions": "openOnSessionStart", "launchBrowser": { "enabled": true, "args": "${auto-detect-url}", "windows": { "command": "cmd.exe", "args": "/C start ${auto-detect-url}" }, "osx": { "command": "open" }, "linux": { "command": "xdg-open" } }, "env": { "ASPNETCORE_ENVIRONMENT": "Development", "ASPNETCORE_URLS": "http://localhost:5005" }, "sourceFileMap": { "/Views": "${workspaceFolder}/Views" } }, { "name": "products", "type": "coreclr", "request": "launch", "preLaunchTask": "build-product", "program": "${workspaceFolder}/DotnetIst.Services.Products/src/Services.Products/bin/Debug/netcoreapp2.2/Services.Products.dll", "args": [], "cwd": "${workspaceFolder}/DotnetIst.Services.Products/src/Services.Products", "stopAtEntry": false, "internalConsoleOptions": "openOnSessionStart", "launchBrowser": { "enabled": true, "args": "${auto-detect-url}", "windows": { "command": "cmd.exe", "args": "/C start ${auto-detect-url}" }, "osx": { "command": "open" }, "linux": { "command": "xdg-open" } }, "env": { "ASPNETCORE_ENVIRONMENT": "Development", "ASPNETCORE_URLS": "http://localhost:5010" }, "sourceFileMap": { "/Views": "${workspaceFolder}/Views" } }, { "name": "orders", "type": "coreclr", "request": "launch", "preLaunchTask": "build-order", "program": "${workspaceFolder}/DotnetIst.Services.Orders/src/Services.Orders/bin/Debug/netcoreapp2.2/Services.Orders.dll", "args": [], "cwd": "${workspaceFolder}/DotnetIst.Services.Orders/src/Services.Orders", "stopAtEntry": false, "internalConsoleOptions": "openOnSessionStart", "launchBrowser": { "enabled": true, "args": "${auto-detect-url}", "windows": { "command": "cmd.exe", "args": "/C start ${auto-detect-url}" }, "osx": { "command": "open" }, "linux": { "command": "xdg-open" } }, "env": { "ASPNETCORE_ENVIRONMENT": "Development", "ASPNETCORE_URLS": "http://localhost:5015" }, "sourceFileMap": { "/Views": "${workspaceFolder}/Views" } }, { "name": "notifications", "type": "coreclr", "request": "launch", "preLaunchTask": "build-notification", "program": "${workspaceFolder}/DotnetIst.Services.Notifications/src/Services.Notifications/bin/Debug/netcoreapp2.2/Services.Notifications.dll", "args": [], "cwd": "${workspaceFolder}/DotnetIst.Services.Notifications/src/Services.Notifications", "stopAtEntry": false, "internalConsoleOptions": "openOnSessionStart", "launchBrowser": { "enabled": true, "args": "${auto-detect-url}", "windows": { "command": "cmd.exe", "args": "/C start ${auto-detect-url}" }, "osx": { "command": "open" }, "linux": { "command": "xdg-open" } }, "env": { "ASPNETCORE_ENVIRONMENT": "Development", "ASPNETCORE_URLS": "http://localhost:5020" }, "sourceFileMap": { "/Views": "${workspaceFolder}/Views" } }, { "name": ".NET Core Attach", "type": "coreclr", "request": "attach", "processId": "${command:pickProcess}" } ], "compounds": [ { "name": "All", "configurations": [ "api", "customer", "products", "orders", "notifications" ] }, { "name": "Api+Customer", "configurations": [ "api", "customer" ] } ] } ================================================ FILE: .vscode/tasks.json ================================================ { "version": "2.0.0", "tasks": [ { "label": "build-api", "command": "dotnet", "type": "process", "args": [ "build", "${workspaceFolder}/DotnetIst.Api/src/Api/Api.csproj" ], "problemMatcher": "$msCompile" }, { "label": "build-customer", "command": "dotnet", "type": "process", "args": [ "build", "${workspaceFolder}/DotnetIst.Services.Customers/src/Services.Customers/Services.Customers.csproj", ], "problemMatcher": "$msCompile" }, { "label": "build-product", "command": "dotnet", "type": "process", "args": [ "build", "${workspaceFolder}/DotnetIst.Services.Products/src/Services.Products/Services.Products.csproj", ], "problemMatcher": "$msCompile" }, { "label": "build-order", "command": "dotnet", "type": "process", "args": [ "build", "${workspaceFolder}/DotnetIst.Services.Orders/src/Services.Orders/Services.Orders.csproj", ], "problemMatcher": "$msCompile" }, { "label": "build-notification", "command": "dotnet", "type": "process", "args": [ "build", "${workspaceFolder}/DotnetIst.Services.Notifications/src/Services.Notifications/Services.Notifications.csproj", ], "problemMatcher": "$msCompile" } ] } ================================================ FILE: DotnetIst.Api/README.MD ================================================ ## Api Gateway todo: use ocelot todo: create bffs for each channel type ================================================ FILE: DotnetIst.Api/src/Api/Api.csproj ================================================ netcoreapp2.2 InProcess ================================================ FILE: DotnetIst.Api/src/Api/Authentication/AuthenticationExtensions.cs ================================================ using System; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.IdentityModel.Tokens; namespace Api.Authentication { public static class AuthenticationExtensions { private const string SecretKey = "SomeStaticAccessKey12345!"; public static void AddJwtAuthentication(this IServiceCollection services) { services .AddScoped() .AddScoped() .AddAuthentication(x => { x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }) .AddJwtBearer(x => { x.Audience = "SomeCustomApp"; x.RequireHttpsMetadata = false; x.SaveToken = true; x.ClaimsIssuer = "dotnetist.api.demo"; x.TokenValidationParameters = new TokenValidationParameters { IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(SecretKey)), ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidateIssuer = false, ValidateAudience = true }; x.Events = new JwtBearerEvents() { OnTokenValidated = (context) => { var name = context.Principal.Identity.Name; if (string.IsNullOrEmpty(name)) { context.Fail("Unauthorized. Please re-login"); } context.HttpContext.Items.Add("CurrentCustomer", new TokenModel { Email = context.Principal.Identity.Name, CustomerId = Guid.Parse( context.Principal.Claims.First(s => s.Type == "CustomerId").Value), }); return Task.CompletedTask; } }; }); } } } ================================================ FILE: DotnetIst.Api/src/Api/Authentication/AuthenticationService.cs ================================================ using System; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using Api.Models; using Microsoft.AspNetCore.Http; using Microsoft.IdentityModel.Tokens; namespace Api.Authentication { public class AuthenticationService : IAuthenticationService { private IHttpContextAccessor _contextAccessor; public AuthenticationService(IHttpContextAccessor contextAccessor) { _contextAccessor = contextAccessor; } public TokenModel GetCurrentUser() => _contextAccessor.HttpContext?.Items?["CurrentCustomer"] != null ? _contextAccessor.HttpContext.Items["CurrentCustomer"] as TokenModel : null; public string GetToken(Customer customer) { var tokenHandler = new JwtSecurityTokenHandler(); var key = Encoding.ASCII.GetBytes("SomeStaticAccessKey12345!"); var tokenDescriptor = new SecurityTokenDescriptor { Audience = "SomeCustomApp", Issuer = "wizlo.api.demo", Subject = new ClaimsIdentity(new Claim[] { new Claim(ClaimTypes.Name, customer.Email), new Claim("CustomerId", customer.Id.ToString()) }), Expires = DateTime.UtcNow.AddDays(1), SigningCredentials = new SigningCredentials( new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature) }; var token = tokenHandler.CreateToken(tokenDescriptor); return tokenHandler.WriteToken(token); } } } ================================================ FILE: DotnetIst.Api/src/Api/Authentication/IAuthenticationService.cs ================================================ using Api.Models; namespace Api.Authentication { public interface IAuthenticationService { string GetToken(Customer customer); } } ================================================ FILE: DotnetIst.Api/src/Api/Authentication/Model/LoginModel.cs ================================================ namespace Api.Authentication { public class LoginModel { public string Email { get; set; } public string Password { get; set; } } } ================================================ FILE: DotnetIst.Api/src/Api/Authentication/Model/TokenModel.cs ================================================ using System; namespace Api.Authentication { public class TokenModel { public Guid CustomerId { get; set; } public string Email { get; set; } } } ================================================ FILE: DotnetIst.Api/src/Api/Commands/Baskets/AddProductToBasket.cs ================================================ using System; using Shared.Messages; using Newtonsoft.Json; namespace Api.Commands.Baskets { [MessageNamespace("customers")] public class AddProductToBasket : ICommand { public Guid ProductId { get; } public int Quantity { get; } public AddProductToBasket(Guid productId, int quantity) { ProductId = productId; Quantity = quantity; } } } ================================================ FILE: DotnetIst.Api/src/Api/Commands/Customers/CreateCustomer.cs ================================================ using System; using Shared.Messages; using Newtonsoft.Json; namespace Api.Commands.Customers { [MessageNamespace("customers")] public class CreateCustomer : ICommand { public Guid Id { get; set; } public string Email { get; set; } public string Password { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Address { get; set; } public CreateCustomer(string email, string password, string firstName, string lastName, string address) { this.Id = Guid.NewGuid(); this.Email = email; this.Password = password; this.FirstName = firstName; this.LastName = lastName; this.Address = address; } } } ================================================ FILE: DotnetIst.Api/src/Api/Commands/Orders/CreateOrder.cs ================================================ using System; using Shared.Messages; using Newtonsoft.Json; namespace Api.Commands.Orders { [MessageNamespace("orders")] public class CreateOrder : ICommand { public Guid Id { get; private set; } public CreateOrder() { Id = Guid.NewGuid(); } } } ================================================ FILE: DotnetIst.Api/src/Api/Controllers/BaseController.cs ================================================ using System; using Api.Authentication; using Shared.Messages; using Shared.RabbitMq; using Microsoft.AspNetCore.Mvc; namespace Api.Controllers { public class BaseController : ControllerBase { protected readonly IBusPublisher BusPublisher; public BaseController(IBusPublisher busPublisher) { BusPublisher = busPublisher; } protected TokenModel CurrentUser { get { return HttpContext.Items["CurrentCustomer"] != null ? HttpContext.Items["CurrentCustomer"] as TokenModel : null; } } protected ICorrelationContext GetContext() { return GetContext(CurrentUser.CustomerId); } //This method is only for AllowAnonymus CustomerController protected ICorrelationContext GetContext(Guid customerId) { return CorrelationContext.Create(Guid.NewGuid(), customerId); } // protected IActionResult Accepted(ICorrelationContext context) // { // // // Response.Headers.Add(OperationHeader, $"checktransactionstatus/{context.Id}"); // return base.Accepted(); // } } } ================================================ FILE: DotnetIst.Api/src/Api/Controllers/BasketController.cs ================================================ using System.Threading.Tasks; using Api.Commands.Baskets; using Shared.RabbitMq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Api.Controllers { [Route("api/[controller]")] [ApiController] [Authorize] public class BasketController : BaseController { public BasketController(IBusPublisher busPublisher) : base(busPublisher) { } [HttpPost] public async Task Post(AddProductToBasket command) { var context = GetContext(); await BusPublisher.SendAsync(command, context); return Accepted(); } } } ================================================ FILE: DotnetIst.Api/src/Api/Controllers/CustomerController.cs ================================================ using System.Threading.Tasks; using Api.Commands.Customers; using Shared.RabbitMq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Api.Controllers { [Route("api/[controller]")] [ApiController] public class CustomerController : BaseController { public CustomerController(IBusPublisher busPublisher) : base(busPublisher) { } [HttpPost] public async Task Post(CreateCustomer command) { var context = GetContext(command.Id); await BusPublisher.SendAsync(command, context); return Accepted(); } } } ================================================ FILE: DotnetIst.Api/src/Api/Controllers/HomeController.cs ================================================ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Api.Controllers { [Route("")] public class HomeController : ControllerBase { [HttpGet] [AllowAnonymous] public IActionResult Get() { return Ok("Api is up."); } } } ================================================ FILE: DotnetIst.Api/src/Api/Controllers/OrderController.cs ================================================ using System.Threading.Tasks; using Api.Commands.Orders; using Shared.RabbitMq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Api.Controllers { [Route("api/[controller]")] [ApiController] [Authorize] public class OrderController : BaseController { public OrderController(IBusPublisher busPublisher) : base(busPublisher) { } [HttpPost] public async Task Post(CreateOrder command) { var context = GetContext(); await BusPublisher.SendAsync(command, context); return Accepted(); } } } ================================================ FILE: DotnetIst.Api/src/Api/Controllers/ProductController.cs ================================================ using System.Threading.Tasks; using Api.Commands.Customers; using Api.HttpServices; using Shared.RabbitMq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Api.Controllers { [Route("api/[controller]")] [ApiController] public class ProductController : ControllerBase { private readonly IProductHttpService _productHttpService; public ProductController(IProductHttpService productHttpService) { _productHttpService = productHttpService; } [HttpGet] public async Task Get() { return Ok(await _productHttpService.GetList()); } } } ================================================ FILE: DotnetIst.Api/src/Api/Controllers/TokenController.cs ================================================ using System.Threading.Tasks; using Api.Authentication; using Api.HttpServices; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Api.Controllers { [Route("api/[controller]")] [ApiController] public class TokenController : ControllerBase { private readonly IAuthenticationService _authService; private readonly ICustomerHttpService _customerHttpService; public TokenController(IAuthenticationService authService, ICustomerHttpService customerHttpService) { _authService = authService; _customerHttpService = customerHttpService; } [HttpPost] [AllowAnonymous] public async Task GenerateToken(LoginModel reqeust) { var customer = await _customerHttpService.GetCustomerByEmail(reqeust.Email); if (customer == null) { return BadRequest("Customer not found."); } if (customer.Password != reqeust.Password) { return BadRequest("Wrong password."); } return Ok(_authService.GetToken(customer)); } } } ================================================ FILE: DotnetIst.Api/src/Api/HttpServices/CustomerHttpService.cs ================================================ using System; using System.Net.Http; using System.Threading.Tasks; using Api.Models; using Newtonsoft.Json; namespace Api.HttpServices { public class CustomerHttpService : ICustomerHttpService { private HttpClient _client { get; } public CustomerHttpService(HttpClient client) { _client = client; } public async Task GetCustomerByEmail(string email) { var request = new HttpRequestMessage(HttpMethod.Get, $"customer/customerbyemail/{email}"); var response = await _client.SendAsync(request); var content = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode) { response.Content.Dispose(); return JsonConvert.DeserializeObject(content); } throw new Exception("Customer service connection error"); } } } ================================================ FILE: DotnetIst.Api/src/Api/HttpServices/ICustomerHttpService.cs ================================================ using System; using System.Threading.Tasks; using Api.Models; namespace Api.HttpServices { public interface ICustomerHttpService { Task GetCustomerByEmail(string CustomerId); } } ================================================ FILE: DotnetIst.Api/src/Api/HttpServices/IProductHttpService.cs ================================================ using System.Collections.Generic; using System.Threading.Tasks; namespace Api.HttpServices { public interface IProductHttpService { Task GetList(); } } ================================================ FILE: DotnetIst.Api/src/Api/HttpServices/ProductHttpService.cs ================================================ using System; using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; namespace Api.HttpServices { public class ProductHttpService : IProductHttpService { private HttpClient _client { get; } public ProductHttpService(HttpClient client) { _client = client; } public async Task GetList() { var request = new HttpRequestMessage(HttpMethod.Get, $"product"); var response = await _client.SendAsync(request); var content = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode) { response.Content.Dispose(); return JsonConvert.DeserializeObject>(content); } throw new Exception("Product service connection error"); } } } ================================================ FILE: DotnetIst.Api/src/Api/Models/Customer.cs ================================================ using System; namespace Api.Models { public class Customer { public Guid Id { get; set; } public string Email { get; set; } public string Password { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Address { get; set; } } } ================================================ FILE: DotnetIst.Api/src/Api/Program.cs ================================================ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Shared.Logging; namespace Api { public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup() .UseLogging("Api"); } } ================================================ FILE: DotnetIst.Api/src/Api/Properties/launchSettings.json ================================================ { "$schema": "http://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:55301", "sslPort": 44356 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "Api": { "commandName": "Project", "launchBrowser": true, "applicationUrl": "http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: DotnetIst.Api/src/Api/Startup.cs ================================================ using System; using System.Net.Http.Headers; using System.Reflection; using Api.Authentication; using Api.HttpServices; using Autofac; using Autofac.Extensions.DependencyInjection; using Shared.Models; using Shared.RabbitMq; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Shared; namespace Api { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); services.AddJwtAuthentication(); var httpServices = Configuration.GetOptions("HttpServices"); services.AddHttpClient(client => { client.BaseAddress = new Uri(httpServices.CustomerHttpServiceUrl); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); }); services.AddHttpClient(client => { client.BaseAddress = new Uri(httpServices.ProductHttpServiceUrl); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); }); return services.BuildContainer(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseAuthentication(); app.UseMvc(); } } } ================================================ FILE: DotnetIst.Api/src/Api/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Debug", "System": "Information", "Microsoft": "Information" } } } ================================================ FILE: DotnetIst.Api/src/Api/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*", "HttpServices": { "CustomerHttpServiceUrl": "http://localhost:5005/api/", "ProductHttpServiceUrl": "http://localhost:5010/api/" }, "rabbitMq": { "namespace": "api", "retries": 3, "retryInterval": 2, "username": "guest", "password": "guest", "virtualHost": "/", "port": 5672, "hostnames": [ "localhost" ], "requestTimeout": "00:00:10", "publishConfirmTimeout": "00:00:01", "recoveryInterval": "00:00:10", "persistentDeliveryMode": true, "autoCloseConnection": true, "automaticRecovery": true, "topologyRecovery": true, "exchange": { "durable": true, "autoDelete": false, "type": "Topic" }, "queue": { "autoDelete": false, "durable": true, "exclusive": false } } } ================================================ FILE: DotnetIst.Services.Customers/README.MD ================================================ **Migration** 0- cd src/Services.Customers 1- *dotnet ef migrations add "migration_name" -o ./Data/Migrations* 2- *dotnet ef database update* ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Commands/AddProductToBasket.cs ================================================ using System; using Shared.Messages; using Newtonsoft.Json; namespace Services.Customers.Commands { public class AddProductToBasket : ICommand { public Guid ProductId { get; } public int Quantity { get; } public AddProductToBasket(Guid productId, int quantity) { ProductId = productId; Quantity = quantity; } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Commands/CreateCustomer.cs ================================================ using System; using Shared.Messages; using Newtonsoft.Json; namespace Services.Customers.Commands { public class CreateCustomer : ICommand { public Guid Id { get; set; } public string Email { get; set; } public string Password { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Address { get; set; } public CreateCustomer(Guid id, string email, string password, string firstName, string lastName, string address) { this.Id = id; this.Password = password; this.Email = email; this.FirstName = firstName; this.LastName = lastName; this.Address = address; } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Controllers/BasketController.cs ================================================ using System; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Services.Customers.Data; namespace Services.Customers.Controllers { [Route("api/[controller]")] [ApiController] public class BasketController : ControllerBase { private readonly CustomerDBContext _dbContext; public BasketController(CustomerDBContext dbContext) { _dbContext = dbContext; } [HttpGet("{customerId}")] public async Task Get(Guid customerId) { return Ok(await _dbContext.Baskets.Include(i => i.Items) .FirstOrDefaultAsync(s => s.CustomerId == customerId)); } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Controllers/CustomerController.cs ================================================ using System; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Services.Customers.Data; namespace Services.Customers.Controllers { [Route("api/[controller]")] [ApiController] public class CustomerController : ControllerBase { private readonly CustomerDBContext _dbContext; public CustomerController(CustomerDBContext dbContext) { _dbContext = dbContext; } [HttpGet("customerbyemail/{email}")] public async Task Get(string email) { return Ok(await _dbContext.Customers .FirstOrDefaultAsync(s => s.Email == email)); } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Controllers/HomeController.cs ================================================ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Services.Customers.Controllers { [Route("")] public class HomeController : ControllerBase { [HttpGet] [AllowAnonymous] public IActionResult Get() { return Ok("Customer service is up."); } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Data/CustomerDBContext.cs ================================================ using Microsoft.EntityFrameworkCore; namespace Services.Customers.Data { public class CustomerDBContext : DbContext { public CustomerDBContext(DbContextOptions options) : base(options) { } public DbSet Baskets { get; set; } public DbSet BasketItems { get; set; } public DbSet Customers { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().ToTable("Basket"); modelBuilder.Entity().HasMany(x => x.Items); modelBuilder.Entity().ToTable("BasketItem"); modelBuilder.Entity().ToTable("Customer"); } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Data/Entity/Basket.cs ================================================ using System; using System.Collections.Generic; using Shared.Models; namespace Services.Customers.Data { public class Basket : BaseEntity { public Guid CustomerId { get; set; } public List Items { get; set; } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Data/Entity/BasketItem.cs ================================================ using System; using Shared.Models; namespace Services.Customers.Data { public class BasketItem : BaseEntity { public Guid BasketId { get; set; } public Guid ProductId { get; set; } public string ProductName { get; set; } public int Quantity { get; set; } public decimal UnitPrice { get; set; } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Data/Entity/Customer.cs ================================================ using System; using Shared.Models; namespace Services.Customers.Data { public class Customer : BaseEntity { public string Email { get; set; } public string Password { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string Address { get; set; } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Data/Migrations/20190308211118_initial.Designer.cs ================================================ // using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; using Services.Customers.Data; namespace Services.Customers.Data.Migrations { [DbContext(typeof(CustomerDBContext))] [Migration("20190308211118_initial")] partial class initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) .HasAnnotation("ProductVersion", "2.2.1-servicing-10028") .HasAnnotation("Relational:MaxIdentifierLength", 63); modelBuilder.Entity("Services.Customers.Data.Basket", b => { b.Property("Id") .ValueGeneratedOnAdd(); b.Property("CreatedAt"); b.Property("CustomerId"); b.ToTable("Basket"); }); modelBuilder.Entity("Services.Customers.Data.BasketItem", b => { b.Property("Id") .ValueGeneratedOnAdd(); b.Property("BasketId"); b.Property("ProductId"); b.Property("ProductName"); b.Property("Quantity"); b.Property("UnitPrice"); b.HasIndex("BasketId"); b.ToTable("BasketItem"); }); modelBuilder.Entity("Services.Customers.Data.Customer", b => { b.Property("Id") .ValueGeneratedOnAdd(); b.Property("Address"); b.Property("Email"); b.Property("FirstName"); b.Property("LastName"); b.Property("Password"); b.ToTable("Customer"); }); modelBuilder.Entity("Services.Customers.Data.BasketItem", b => { b.HasOne("Services.Customers.Data.Basket") .WithMany("Items") .HasForeignKey("BasketId") .OnDelete(DeleteBehavior.Cascade); }); #pragma warning restore 612, 618 } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Data/Migrations/20190308211118_initial.cs ================================================ using System; using Microsoft.EntityFrameworkCore.Migrations; namespace Services.Customers.Data.Migrations { public partial class initial : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "Basket", columns: table => new { Id = table.Column(nullable: false), CustomerId = table.Column(nullable: false), CreatedAt = table.Column(nullable: false) }, constraints: table => { table.PrimaryKey("PK_Basket", x => x.Id); }); migrationBuilder.CreateTable( name: "Customer", columns: table => new { Id = table.Column(nullable: false), Email = table.Column(nullable: true), Password = table.Column(nullable: true), FirstName = table.Column(nullable: true), LastName = table.Column(nullable: true), Address = table.Column(nullable: true) }, constraints: table => { table.PrimaryKey("PK_Customer", x => x.Id); }); migrationBuilder.CreateTable( name: "BasketItem", columns: table => new { Id = table.Column(nullable: false), BasketId = table.Column(nullable: false), ProductId = table.Column(nullable: false), ProductName = table.Column(nullable: true), Quantity = table.Column(nullable: false), UnitPrice = table.Column(nullable: false) }, constraints: table => { table.PrimaryKey("PK_BasketItem", x => x.Id); table.ForeignKey( name: "FK_BasketItem_Basket_BasketId", column: x => x.BasketId, principalTable: "Basket", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateIndex( name: "IX_BasketItem_BasketId", table: "BasketItem", column: "BasketId"); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( name: "BasketItem"); migrationBuilder.DropTable( name: "Customer"); migrationBuilder.DropTable( name: "Basket"); } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Data/Migrations/20190308222102_base_entity.Designer.cs ================================================ // using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; using Services.Customers.Data; namespace Services.Customers.Data.Migrations { [DbContext(typeof(CustomerDBContext))] [Migration("20190308222102_base_entity")] partial class base_entity { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) .HasAnnotation("ProductVersion", "2.2.1-servicing-10028") .HasAnnotation("Relational:MaxIdentifierLength", 63); modelBuilder.Entity("Services.Customers.Data.Basket", b => { b.Property("Id") .ValueGeneratedOnAdd(); b.Property("CustomerId"); b.Property("UpdateDate"); b.ToTable("Basket"); }); modelBuilder.Entity("Services.Customers.Data.BasketItem", b => { b.Property("Id") .ValueGeneratedOnAdd(); b.Property("BasketId"); b.Property("ProductId"); b.Property("ProductName"); b.Property("Quantity"); b.Property("UnitPrice"); b.Property("UpdateDate"); b.HasIndex("BasketId"); b.ToTable("BasketItem"); }); modelBuilder.Entity("Services.Customers.Data.Customer", b => { b.Property("Id") .ValueGeneratedOnAdd(); b.Property("Address"); b.Property("Email"); b.Property("FirstName"); b.Property("LastName"); b.Property("Password"); b.Property("UpdateDate"); b.ToTable("Customer"); }); modelBuilder.Entity("Services.Customers.Data.BasketItem", b => { b.HasOne("Services.Customers.Data.Basket") .WithMany("Items") .HasForeignKey("BasketId") .OnDelete(DeleteBehavior.Cascade); }); #pragma warning restore 612, 618 } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Data/Migrations/20190308222102_base_entity.cs ================================================ using System; using Microsoft.EntityFrameworkCore.Migrations; namespace Services.Customers.Data.Migrations { public partial class base_entity : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.RenameColumn( name: "CreatedAt", table: "Basket", newName: "UpdateDate"); migrationBuilder.AddColumn( name: "UpdateDate", table: "Customer", nullable: false, defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); migrationBuilder.AddColumn( name: "UpdateDate", table: "BasketItem", nullable: false, defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropColumn( name: "UpdateDate", table: "Customer"); migrationBuilder.DropColumn( name: "UpdateDate", table: "BasketItem"); migrationBuilder.RenameColumn( name: "UpdateDate", table: "Basket", newName: "CreatedAt"); } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Data/Migrations/CustomerDBContextModelSnapshot.cs ================================================ // using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; using Services.Customers.Data; namespace Services.Customers.Data.Migrations { [DbContext(typeof(CustomerDBContext))] partial class CustomerDBContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) .HasAnnotation("ProductVersion", "2.2.1-servicing-10028") .HasAnnotation("Relational:MaxIdentifierLength", 63); modelBuilder.Entity("Services.Customers.Data.Basket", b => { b.Property("Id") .ValueGeneratedOnAdd(); b.Property("CustomerId"); b.Property("UpdateDate"); b.ToTable("Basket"); }); modelBuilder.Entity("Services.Customers.Data.BasketItem", b => { b.Property("Id") .ValueGeneratedOnAdd(); b.Property("BasketId"); b.Property("ProductId"); b.Property("ProductName"); b.Property("Quantity"); b.Property("UnitPrice"); b.Property("UpdateDate"); b.HasIndex("BasketId"); b.ToTable("BasketItem"); }); modelBuilder.Entity("Services.Customers.Data.Customer", b => { b.Property("Id") .ValueGeneratedOnAdd(); b.Property("Address"); b.Property("Email"); b.Property("FirstName"); b.Property("LastName"); b.Property("Password"); b.Property("UpdateDate"); b.ToTable("Customer"); }); modelBuilder.Entity("Services.Customers.Data.BasketItem", b => { b.HasOne("Services.Customers.Data.Basket") .WithMany("Items") .HasForeignKey("BasketId") .OnDelete(DeleteBehavior.Cascade); }); #pragma warning restore 612, 618 } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Data/SeedData.cs ================================================ using System; using System.Linq; using Microsoft.Extensions.DependencyInjection; namespace Services.Customers.Data { public static class SeedData { public static void Initialize(IServiceProvider serviceProvider) { var context = serviceProvider.GetRequiredService(); context.Database.EnsureCreated(); } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Events/OrderCompleted.cs ================================================ using System; using Shared.Messages; using Newtonsoft.Json; namespace Services.Customers.Events { public class OrderCompleted : IEvent { public Guid Id { get; } public OrderCompleted(Guid id) { Id = id; } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Events/ProductAddedToBasket.cs ================================================ using System; using Shared.Messages; using Newtonsoft.Json; namespace Services.Customers.Events { public class ProductAddedToBasket : IEvent { public Guid ProductId { get; } public int Quantity { get; } public ProductAddedToBasket(Guid productId, int quantity) { ProductId = productId; Quantity = quantity; } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Handlers/AddProductToBasketHandler.cs ================================================ using System; using System.Linq; using System.Threading.Tasks; using Shared.MessageHandlers; using Shared.RabbitMq; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Services.Customers.Commands; using Services.Customers.Data; using Services.Customers.Events; using Services.Customers.HttpServices; namespace Services.Customers.Handlers { public class AddProductToBasketHandler : ICommandHandler { private readonly IBusPublisher _busPublisher; private readonly CustomerDBContext _dbContext; private readonly IProductHttpService _productHttpService; private readonly ILogger _logger; public AddProductToBasketHandler(IBusPublisher busPublisher, CustomerDBContext dbContext, IProductHttpService productHttpService, ILogger logger ) { _productHttpService = productHttpService; _logger = logger; _busPublisher = busPublisher; _dbContext = dbContext; } public async Task HandleAsync(AddProductToBasket command, ICorrelationContext context) { // Warning: Customer service needs Product service's data. // What if Product service can't response for a while? (assume no retry policy) // DDD - sharing data betwwen bounded cotext var product = await _productHttpService.GetAsync(command.ProductId); if (product == null) throw new Exception($"Product not found. Id: {command.ProductId}"); var basket = await _dbContext.Baskets.FirstOrDefaultAsync(q => q.CustomerId == context.CustomerId); if (basket == null) throw new Exception($"Basket not found for customer: {context.CustomerId}"); var basketItem = await _dbContext.BasketItems.FirstOrDefaultAsync(q => q.ProductId == command.ProductId); if (basketItem != null) { basketItem.Quantity += command.Quantity; } else { _dbContext.BasketItems.Add( new BasketItem { BasketId = basket.Id, ProductId = command.ProductId, Quantity = command.Quantity, ProductName = product.Name, UnitPrice = product.Price }); } await _dbContext.SaveChangesAsync(); // no event, just logging. _logger.LogInformation($"[Local Transaction] : {command.Quantity} {product.Name} added to basket. CorrelationId: {context.CorrelationId}"); } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Handlers/CreateCustomerdHandler.cs ================================================ using System; using System.Threading.Tasks; using Shared.MessageHandlers; using Shared.RabbitMq; using Microsoft.Extensions.Logging; using Services.Customers.Commands; using Services.Customers.Data; namespace Services.Customers.Handlers { public class CreateCustomerdHandler : ICommandHandler { private readonly IBusPublisher _busPublisher; private readonly CustomerDBContext _dbContext; private readonly ILogger _logger; public CreateCustomerdHandler(IBusPublisher busPublisher, CustomerDBContext dbContext, ILogger logger) { _logger = logger; _busPublisher = busPublisher; _dbContext = dbContext; } public async Task HandleAsync(CreateCustomer _event, ICorrelationContext context) { _dbContext.Customers.Add(new Customer { Id = _event.Id, Password = _event.Password, Email = _event.Email, FirstName = _event.FirstName, LastName = _event.LastName, Address = _event.Address }); _dbContext.Baskets.Add(new Basket { CustomerId = _event.Id }); await _dbContext.SaveChangesAsync(); _logger.LogInformation($"[Local Transaction] : Customer created. CorrelataionId: {context.CorrelationId}"); } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Handlers/OrderCompletedHandler.cs ================================================ using System.Threading.Tasks; using Shared.MessageHandlers; using Shared.RabbitMq; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Services.Customers.Data; using Services.Customers.Events; namespace Services.Customers.Handlers { public class OrderCompletedHandler : IEventHandler { private readonly IBusPublisher _busPublisher; private readonly CustomerDBContext _dbContext; private readonly ILogger _logger; public OrderCompletedHandler(IBusPublisher busPublisher, CustomerDBContext dbContext, ILogger logger) { _logger = logger; _busPublisher = busPublisher; _dbContext = dbContext; } public async Task HandleAsync(OrderCompleted _event, ICorrelationContext context) { var basket = await _dbContext.Baskets .Include(i => i.Items) .FirstOrDefaultAsync(q => q.CustomerId == context.CustomerId); _dbContext.BasketItems.RemoveRange(basket.Items); await _dbContext.SaveChangesAsync(); //no event, just logging _logger.LogInformation($"[Local Transaction] : Basket items cleared. CorrelationId: {context.CorrelationId}"); } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/HttpServices/IProductHttpService.cs ================================================ using System; using System.Threading.Tasks; using Services.Customers.Data; using Services.Customers.Models; namespace Services.Customers.HttpServices { public interface IProductHttpService { Task GetAsync(Guid id); } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/HttpServices/ProductHttpService.cs ================================================ using System; using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; using Services.Customers.Data; using Services.Customers.Models; namespace Services.Customers.HttpServices { public class ProductHttpService : IProductHttpService { private HttpClient _client { get; } public ProductHttpService(HttpClient client) { _client = client; } public async Task GetAsync(Guid id) { var request = new HttpRequestMessage(HttpMethod.Get, $"product/{id}"); var response = await _client.SendAsync(request); var content = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode) { response.Content.Dispose(); return JsonConvert.DeserializeObject(content); } throw new Exception("Product service connection error"); } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Models/Product.cs ================================================ using System; namespace Services.Customers.Models { public class Product { public Guid Id { get; set; } public string Name { get; set; } public decimal Price { get; set; } public int Quantity { get; set; } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Program.cs ================================================ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Shared.Logging; namespace Services.Customers { public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup() .UseLogging("Customer.Service"); } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Properties/launchSettings.json ================================================ { "$schema": "http://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:5834", "sslPort": 44386 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "src": { "commandName": "Project", "launchBrowser": true, "applicationUrl": "http://localhost:5005", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Services.Customers.csproj ================================================ netcoreapp2.2 InProcess ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/Startup.cs ================================================ using System; using System.Net.Http.Headers; using Shared.Models; using Shared.RabbitMq; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Services.Customers.Commands; using Services.Customers.Data; using Shared; using Services.Customers.HttpServices; using Services.Customers.Events; namespace Services.Customers { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddDbContext(options => options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")) ); var httpServices = Configuration.GetOptions("HttpServices"); services.AddHttpClient(client => { client.BaseAddress = new Uri(httpServices.ProductHttpServiceUrl); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); return services.BuildContainer(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(); app.UseRabbitMq() .SubscribeCommand() .SubscribeCommand() .SubscribeEvent(); SeedData.Initialize(app.ApplicationServices); } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Debug", "System": "Information", "Microsoft": "Information" } } } ================================================ FILE: DotnetIst.Services.Customers/src/Services.Customers/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnection": "Host=localhost;Port=5432;Username=postgres;Password=postgres;Database=Customers;" }, "HttpServices": { "ProductHttpServiceUrl": "http://localhost:5010/api/" }, "rabbitMq": { "namespace": "customers", "retries": 3, "retryInterval": 2, "username": "guest", "password": "guest", "virtualHost": "/", "port": 5672, "hostnames": [ "localhost" ], "requestTimeout": "00:00:10", "publishConfirmTimeout": "00:00:01", "recoveryInterval": "00:00:10", "persistentDeliveryMode": true, "autoCloseConnection": true, "automaticRecovery": true, "topologyRecovery": true, "exchange": { "durable": true, "autoDelete": false, "type": "Topic" }, "queue": { "autoDelete": false, "durable": true, "exclusive": false } } } ================================================ FILE: DotnetIst.Services.Notifications/README.MD ================================================ **NotificationService** ================================================ FILE: DotnetIst.Services.Notifications/src/Services.Notifications/Controllers/HomeController.cs ================================================ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Services.Notifications.Controllers { [Route("")] public class HomeController : ControllerBase { [HttpGet] [AllowAnonymous] public IActionResult Get() { return Ok("Notification service is up."); } } } ================================================ FILE: DotnetIst.Services.Notifications/src/Services.Notifications/Events/OrderCompleted.cs ================================================ using System; using Shared.Messages; using Newtonsoft.Json; namespace Services.Notifications.Events { [MessageNamespace("customers")] public class OrderCompleted : IEvent { public Guid Id { get; } public OrderCompleted(Guid id) { Id = id; } } } ================================================ FILE: DotnetIst.Services.Notifications/src/Services.Notifications/Events/OrderFailed.cs ================================================ using System; using Shared.Messages; using Newtonsoft.Json; namespace Services.Notifications.Events { public class OrderFailed : IEvent { public Guid Id { get; } public OrderFailed(Guid id) { Id = id; } } } ================================================ FILE: DotnetIst.Services.Notifications/src/Services.Notifications/Handlers/OrderCompletedHandler.cs ================================================ using System.Threading.Tasks; using Shared.MessageHandlers; using Shared.RabbitMq; using Microsoft.Extensions.Logging; using Services.Notifications.Events; namespace Services.Notifications.Handlers { public class OrderCompletedHandler : IEventHandler { private readonly ILogger _logger; public OrderCompletedHandler(ILogger logger) { _logger = logger; } public async Task HandleAsync(OrderCompleted _event, ICorrelationContext context) { // Order completed sms/mail/push... _logger.LogInformation($"[Local Transaction] : Notification sent for Created Order. CorrelationId: {context.CorrelationId}"); await Task.CompletedTask; } } } ================================================ FILE: DotnetIst.Services.Notifications/src/Services.Notifications/Handlers/OrderFailedHandler.cs ================================================ using System.Threading.Tasks; using Shared.MessageHandlers; using Shared.RabbitMq; using Microsoft.Extensions.Logging; using Services.Notifications.Events; namespace Services.Notifications.Handlers { public class OrderFailedHandler : IEventHandler { private readonly ILogger _logger; public OrderFailedHandler(ILogger logger) { _logger = logger; } public async Task HandleAsync(OrderFailed _event, ICorrelationContext context) { // Order failed sms/mail/push... _logger.LogInformation($"[Local Transaction] : Notification sent for Failed Order. CorrelationId: {context.CorrelationId}"); await Task.CompletedTask; } } } ================================================ FILE: DotnetIst.Services.Notifications/src/Services.Notifications/Program.cs ================================================ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Shared.Logging; namespace Services.Notifications { public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup() .UseLogging("Notification.Service"); } } ================================================ FILE: DotnetIst.Services.Notifications/src/Services.Notifications/Properties/launchSettings.json ================================================ { "$schema": "http://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:34801", "sslPort": 44346 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "Services.Notifications": { "commandName": "Project", "launchBrowser": true, "applicationUrl": "http://localhost:5020", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: DotnetIst.Services.Notifications/src/Services.Notifications/Services.Notifications.csproj ================================================ netcoreapp2.2 InProcess ================================================ FILE: DotnetIst.Services.Notifications/src/Services.Notifications/Startup.cs ================================================ using System; using Shared.RabbitMq; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Services.Notifications.Events; using Shared; namespace Services.Notifications { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); return services.BuildContainer(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(); app.UseRabbitMq() .SubscribeEvent(_namespace: "customers") .SubscribeEvent(); } } } ================================================ FILE: DotnetIst.Services.Notifications/src/Services.Notifications/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Debug", "System": "Information", "Microsoft": "Information" } } } ================================================ FILE: DotnetIst.Services.Notifications/src/Services.Notifications/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*", "rabbitMq": { "namespace": "notifications", "retries": 3, "retryInterval": 2, "username": "guest", "password": "guest", "virtualHost": "/", "port": 5672, "hostnames": [ "localhost" ], "requestTimeout": "00:00:10", "publishConfirmTimeout": "00:00:01", "recoveryInterval": "00:00:10", "persistentDeliveryMode": true, "autoCloseConnection": true, "automaticRecovery": true, "topologyRecovery": true, "exchange": { "durable": true, "autoDelete": false, "type": "Topic" }, "queue": { "autoDelete": false, "durable": true, "exclusive": false } } } ================================================ FILE: DotnetIst.Services.Orders/README.md ================================================ **Migration** 0- cd src/Services.Products 1- *dotnet ef migrations add "migration_name" -o ./Data/Migrations* 2- *dotnet ef database update* ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Commands/CreateOrder.cs ================================================ using System; using Shared.Messages; using Newtonsoft.Json; namespace Services.Orders.Commands { public class CreateOrder : ICommand { public Guid Id { get; } public CreateOrder(Guid id) { this.Id = id; } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Controllers/HomeController.cs ================================================ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Services.Orders.Controllers { [Route("")] public class HomeController : ControllerBase { [HttpGet] [AllowAnonymous] public IActionResult Get() { return Ok("Order service is up."); } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Data/Entity/Order.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Shared.Models; namespace Services.Orders.Data { public class Order : BaseEntity { public Guid CustomerId { get; set; } public List Items { get; set; } public decimal TotalAmount { get; set; } public OrderStatus Status { get; set; } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Data/Entity/OrderItem.cs ================================================ using System; using Shared.Models; namespace Services.Orders.Data { public class OrderItem : BaseEntity { public Guid OrderId { get; set; } public Guid ProductId { get; set; } public string Name { get; set; } public int Quantity { get; set; } public decimal UnitPrice { get; set; } public decimal TotalPrice => Quantity * UnitPrice; } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Data/Entity/OrderStatus.cs ================================================ namespace Services.Orders.Data { public enum OrderStatus { Created = 0, Completed = 1, Failed = 2 } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Data/Migrations/20190308211841_initial.Designer.cs ================================================ // using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; using Services.Customers.Data; namespace Services.Orders.Data.Migrations { [DbContext(typeof(OrderDBContext))] [Migration("20190308211841_initial")] partial class initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) .HasAnnotation("ProductVersion", "2.2.1-servicing-10028") .HasAnnotation("Relational:MaxIdentifierLength", 63); modelBuilder.Entity("Services.Orders.Data.Order", b => { b.Property("Id") .ValueGeneratedOnAdd(); b.Property("CustomerId"); b.Property("Status"); b.Property("TotalAmount"); b.ToTable("Order"); }); modelBuilder.Entity("Services.Orders.Data.OrderItem", b => { b.Property("Id") .ValueGeneratedOnAdd(); b.Property("Name"); b.Property("OrderId"); b.Property("ProductId"); b.Property("Quantity"); b.Property("UnitPrice"); b.HasIndex("OrderId"); b.ToTable("OrderItem"); }); modelBuilder.Entity("Services.Orders.Data.OrderItem", b => { b.HasOne("Services.Orders.Data.Order") .WithMany("Items") .HasForeignKey("OrderId") .OnDelete(DeleteBehavior.Cascade); }); #pragma warning restore 612, 618 } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Data/Migrations/20190308211841_initial.cs ================================================ using System; using Microsoft.EntityFrameworkCore.Migrations; namespace Services.Orders.Data.Migrations { public partial class initial : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "Order", columns: table => new { Id = table.Column(nullable: false), CustomerId = table.Column(nullable: false), TotalAmount = table.Column(nullable: false), Status = table.Column(nullable: false) }, constraints: table => { table.PrimaryKey("PK_Order", x => x.Id); }); migrationBuilder.CreateTable( name: "OrderItem", columns: table => new { Id = table.Column(nullable: false), OrderId = table.Column(nullable: false), ProductId = table.Column(nullable: false), Name = table.Column(nullable: true), Quantity = table.Column(nullable: false), UnitPrice = table.Column(nullable: false) }, constraints: table => { table.PrimaryKey("PK_OrderItem", x => x.Id); table.ForeignKey( name: "FK_OrderItem_Order_OrderId", column: x => x.OrderId, principalTable: "Order", principalColumn: "Id", onDelete: ReferentialAction.Cascade); }); migrationBuilder.CreateIndex( name: "IX_OrderItem_OrderId", table: "OrderItem", column: "OrderId"); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( name: "OrderItem"); migrationBuilder.DropTable( name: "Order"); } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Data/Migrations/20190308222202_base_entity.Designer.cs ================================================ // using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; using Services.Customers.Data; namespace Services.Orders.Data.Migrations { [DbContext(typeof(OrderDBContext))] [Migration("20190308222202_base_entity")] partial class base_entity { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) .HasAnnotation("ProductVersion", "2.2.1-servicing-10028") .HasAnnotation("Relational:MaxIdentifierLength", 63); modelBuilder.Entity("Services.Orders.Data.Order", b => { b.Property("Id") .ValueGeneratedOnAdd(); b.Property("CustomerId"); b.Property("Status"); b.Property("TotalAmount"); b.Property("UpdateDate"); b.ToTable("Order"); }); modelBuilder.Entity("Services.Orders.Data.OrderItem", b => { b.Property("Id") .ValueGeneratedOnAdd(); b.Property("Name"); b.Property("OrderId"); b.Property("ProductId"); b.Property("Quantity"); b.Property("UnitPrice"); b.Property("UpdateDate"); b.HasIndex("OrderId"); b.ToTable("OrderItem"); }); modelBuilder.Entity("Services.Orders.Data.OrderItem", b => { b.HasOne("Services.Orders.Data.Order") .WithMany("Items") .HasForeignKey("OrderId") .OnDelete(DeleteBehavior.Cascade); }); #pragma warning restore 612, 618 } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Data/Migrations/20190308222202_base_entity.cs ================================================ using System; using Microsoft.EntityFrameworkCore.Migrations; namespace Services.Orders.Data.Migrations { public partial class base_entity : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AddColumn( name: "UpdateDate", table: "OrderItem", nullable: false, defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); migrationBuilder.AddColumn( name: "UpdateDate", table: "Order", nullable: false, defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropColumn( name: "UpdateDate", table: "OrderItem"); migrationBuilder.DropColumn( name: "UpdateDate", table: "Order"); } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Data/Migrations/OrderDBContextModelSnapshot.cs ================================================ // using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; using Services.Customers.Data; namespace Services.Orders.Data.Migrations { [DbContext(typeof(OrderDBContext))] partial class OrderDBContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) .HasAnnotation("ProductVersion", "2.2.1-servicing-10028") .HasAnnotation("Relational:MaxIdentifierLength", 63); modelBuilder.Entity("Services.Orders.Data.Order", b => { b.Property("Id") .ValueGeneratedOnAdd(); b.Property("CustomerId"); b.Property("Status"); b.Property("TotalAmount"); b.Property("UpdateDate"); b.ToTable("Order"); }); modelBuilder.Entity("Services.Orders.Data.OrderItem", b => { b.Property("Id") .ValueGeneratedOnAdd(); b.Property("Name"); b.Property("OrderId"); b.Property("ProductId"); b.Property("Quantity"); b.Property("UnitPrice"); b.Property("UpdateDate"); b.HasIndex("OrderId"); b.ToTable("OrderItem"); }); modelBuilder.Entity("Services.Orders.Data.OrderItem", b => { b.HasOne("Services.Orders.Data.Order") .WithMany("Items") .HasForeignKey("OrderId") .OnDelete(DeleteBehavior.Cascade); }); #pragma warning restore 612, 618 } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Data/OrderDBContext.cs ================================================ using Microsoft.EntityFrameworkCore; using Services.Orders.Data; namespace Services.Customers.Data { public class OrderDBContext : DbContext { public OrderDBContext(DbContextOptions options) : base(options) { } public DbSet Orders { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().ToTable("Order"); modelBuilder.Entity().HasMany(x => x.Items); } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Data/SeedData.cs ================================================ using System; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Services.Customers.Data; namespace Services.Orders.Data { public static class SeedData { public static void Initialize(IServiceProvider serviceProvider) { var context = serviceProvider.GetRequiredService(); context.Database.EnsureCreated(); } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Events/OrderCompleted.cs ================================================ using System; using Shared.Messages; using Newtonsoft.Json; namespace Services.Orders.Events { [MessageNamespace("customers")] public class OrderCompleted : IEvent { public Guid Id { get; } public OrderCompleted(Guid id) { Id = id; } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Events/OrderCreated.cs ================================================ using System; using System.Collections.Generic; using Shared.Messages; using Newtonsoft.Json; namespace Services.Orders.Events { [MessageNamespace("products")] public class OrderCreated : IEvent { public Guid Id { get; } public IDictionary Products { get; } // id, quantity public OrderCreated(Guid id, IDictionary products) { Id = id; Products = products; } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Events/OrderFailed.cs ================================================ using System; using Shared.Messages; namespace Services.Orders.Events { [MessageNamespace("notifications")] public class OrderFailed : IEvent { public Guid OrderId { get; set; } public OrderFailed(Guid orderId) { this.OrderId = orderId; } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Events/ProductsReserveFailed.cs ================================================ using System; using Shared.Messages; namespace Services.Orders.Events { public class ProductsReserveFailed : IEvent { public Guid OrderId { get; set; } public ProductsReserveFailed(Guid orderId) { this.OrderId = orderId; } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Events/ProductsReserved.cs ================================================ using System; using System.Collections.Generic; using Shared.Messages; namespace Services.Orders.Events { public class ProductsReserved : IEvent { public Guid OrderId { get; set; } public IDictionary Products { get; set; } public ProductsReserved(Guid orderId, IDictionary products) { OrderId = orderId; Products = products; } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Handlers/CreateOrderHandler.cs ================================================ using System; using System.Linq; using System.Threading.Tasks; using Shared.MessageHandlers; using Shared.RabbitMq; using Microsoft.Extensions.Logging; using Services.Customers.Data; using Services.Orders.Commands; using Services.Orders.Data; using Services.Orders.Events; using Services.Orders.HttpServices; namespace Services.Orders.Handlers { public class CreateOrderHandler : ICommandHandler { private readonly IBusPublisher _busPublisher; private readonly ICustomerHttpService _customerHttpService; private readonly OrderDBContext _dbContext; private readonly ILogger _logger; public CreateOrderHandler(IBusPublisher busPublisher, ICustomerHttpService customerHttpService, OrderDBContext dbContext, ILogger logger) { _busPublisher = busPublisher; _customerHttpService = customerHttpService; _dbContext = dbContext; _logger = logger; } public async Task HandleAsync(CreateOrder command, ICorrelationContext context) { // Warning: Order service needs Customer service's data. // What if Customer service can't response for a while? (assume no retry policy) var basket = await _customerHttpService.GetBasket(context.CustomerId); var items = basket.Items.Select(i => new OrderItem { Id = Guid.NewGuid(), OrderId = command.Id, ProductId = i.ProductId, Name = i.ProductName, Quantity = i.Quantity, UnitPrice = i.UnitPrice }).ToList(); var order = new Order { Status = OrderStatus.Created, Id = command.Id, CustomerId = context.CustomerId, Items = items, TotalAmount = items.Sum(s => s.TotalPrice) }; _dbContext.Orders.Add(order); await _dbContext.SaveChangesAsync(); _logger.LogInformation($"[Local Transaction] : Order created. CorrelationId: {context.CorrelationId}"); await _busPublisher.PublishAsync(new OrderCreated( command.Id, items.ToDictionary(i => i.ProductId, i => i.Quantity)), context); } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Handlers/ProductsReserveFailedHandler.cs ================================================ using System; using System.Threading.Tasks; using Shared.MessageHandlers; using Shared.RabbitMq; using Microsoft.Extensions.Logging; using Services.Customers.Data; using Services.Orders.Data; using Services.Orders.Events; namespace Services.Orders.Handlers { public class ProductsReserveFailedHandler : IEventHandler { private readonly IBusPublisher _busPublisher; private readonly OrderDBContext _dbContext; private readonly ILogger _logger; public ProductsReserveFailedHandler(IBusPublisher busPublisher, OrderDBContext dbContext, ILogger logger) { _logger = logger; _busPublisher = busPublisher; _dbContext = dbContext; } public async Task HandleAsync(ProductsReserveFailed _event, ICorrelationContext context) { var order = await _dbContext.Orders.FindAsync(_event.OrderId); order.Status = OrderStatus.Failed; order.UpdateDate = DateTime.Now; await _dbContext.SaveChangesAsync(); _logger.LogInformation($"[Local Transaction] : Order failed. CorrelationId: {context.CorrelationId}"); await _busPublisher.PublishAsync(new OrderFailed(_event.OrderId), context); } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Handlers/ProductsReservedHandler.cs ================================================ using System.Threading.Tasks; using Shared.MessageHandlers; using Shared.RabbitMq; using Microsoft.Extensions.Logging; using Services.Customers.Data; using Services.Orders.Data; using Services.Orders.Events; namespace Services.Orders.Handlers { public class ProductsReservedHandler : IEventHandler { private readonly IBusPublisher _busPublisher; private readonly OrderDBContext _dbContext; private readonly ILogger _logger; public ProductsReservedHandler(IBusPublisher busPublisher, OrderDBContext dbContext, ILogger logger) { _logger = logger; _busPublisher = busPublisher; _dbContext = dbContext; } public async Task HandleAsync(ProductsReserved _event, ICorrelationContext context) { var order = await _dbContext.Orders.FindAsync(_event.OrderId); order.Status = OrderStatus.Completed; await _dbContext.SaveChangesAsync(); _logger.LogInformation($"[Local Transaction] : Order completed. CorrelationId: {context.CorrelationId}"); await _busPublisher.PublishAsync(new OrderCompleted(_event.OrderId), context); } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/HttpServices/CustomerHttpService.cs ================================================ using System; using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; using Services.Orders.Models; namespace Services.Orders.HttpServices { public class CustomerHttpService : ICustomerHttpService { private HttpClient _client { get; } public CustomerHttpService(HttpClient client) { _client = client; } public async Task GetBasket(Guid customerId) { var request = new HttpRequestMessage(HttpMethod.Get, $"basket/{customerId}"); var response = await _client.SendAsync(request); var content = await response.Content.ReadAsStringAsync(); if (response.IsSuccessStatusCode) { response.Content.Dispose(); return JsonConvert.DeserializeObject(content); } throw new Exception("Customer service connection error"); } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/HttpServices/ICustomerHttpService.cs ================================================ using System; using System.Threading.Tasks; using Services.Orders.Models; namespace Services.Orders.HttpServices { public interface ICustomerHttpService { Task GetBasket(Guid CustomerId); } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Models/Basket.cs ================================================ using System; using System.Collections.Generic; namespace Services.Orders.Models { public class Basket { public Guid Id { get; set; } public Guid CustomerId { get; set; } public DateTime CreatedAt { get; set; } public List Items { get; set; } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Models/BasketItem.cs ================================================ using System; namespace Services.Orders.Models { public class BasketItem { public Guid Id { get; set; } public Guid BasketId { get; set; } public Guid ProductId { get; set; } public string ProductName { get; set; } public int Quantity { get; set; } public decimal UnitPrice { get; set; } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Program.cs ================================================ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Shared.Logging; namespace Services.Orders { public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup() .UseLogging("Order.Service"); } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Properties/launchSettings.json ================================================ { "$schema": "http://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:35766", "sslPort": 44314 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "Services.Orders": { "commandName": "Project", "launchBrowser": true, "applicationUrl": "http://localhost:5015", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Services.Orders.csproj ================================================ netcoreapp2.2 InProcess ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/Startup.cs ================================================ using System; using System.Net.Http.Headers; using Shared.Models; using Shared.RabbitMq; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Services.Orders.Commands; using Services.Orders.Data; using Shared; using Services.Orders.HttpServices; using Services.Customers.Data; using Services.Orders.Events; namespace Services.Orders { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddDbContext(options => options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")) ); var httpServices = Configuration.GetOptions("HttpServices"); services.AddHttpClient(client => { client.BaseAddress = new Uri(httpServices.CustomerHttpServiceUrl); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); }); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); return services.BuildContainer(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(); app.UseRabbitMq() .SubscribeCommand() .SubscribeEvent() .SubscribeEvent(); SeedData.Initialize(app.ApplicationServices); } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Debug", "System": "Information", "Microsoft": "Information" } } } ================================================ FILE: DotnetIst.Services.Orders/src/Services.Orders/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnection": "Host=localhost;Port=5432;Username=postgres;Password=postgres;Database=Orders;" }, "HttpServices": { "CustomerHttpServiceUrl": "http://localhost:5005/api/" }, "rabbitMq": { "namespace": "orders", "retries": 3, "retryInterval": 2, "username": "guest", "password": "guest", "virtualHost": "/", "port": 5672, "hostnames": [ "localhost" ], "requestTimeout": "00:00:10", "publishConfirmTimeout": "00:00:01", "recoveryInterval": "00:00:10", "persistentDeliveryMode": true, "autoCloseConnection": true, "automaticRecovery": true, "topologyRecovery": true, "exchange": { "durable": true, "autoDelete": false, "type": "Topic" }, "queue": { "autoDelete": false, "durable": true, "exclusive": false } } } ================================================ FILE: DotnetIst.Services.Products/README.md ================================================ **Migration** 0- .csproj path 1- *dotnet ef migrations add "migration_name" -o ./Data/Migrations* 2- *dotnet ef database update* ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/Controllers/HomeController.cs ================================================ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Services.Products.Controllers { [Route("")] public class HomeController : ControllerBase { [HttpGet] [AllowAnonymous] public IActionResult Get() { return Ok("Product service is up."); } } } ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/Controllers/ProductController.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Services.Customers.Data; using Services.Products.Data; namespace Services.Products.Controllers { [Route("api/[controller]")] [ApiController] public class ProductController : ControllerBase { private readonly ProductDBContext _dbContext; public ProductController(ProductDBContext dbContext) { _dbContext = dbContext; } [HttpGet("{id}")] public async Task Get(Guid id) { return Ok(await _dbContext.Products.FindAsync(id)); } [HttpGet] public async Task Get() { return Ok(await _dbContext.Products.ToListAsync()); } } } ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/Data/Entity/Product.cs ================================================ using System; using Shared.Models; namespace Services.Products.Data { public class Product : BaseEntity { public string Name { get; set; } public decimal Price { get; set; } public int Quantity { get; set; } } } ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/Data/Migrations/20190308212022_initial.Designer.cs ================================================ // using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; using Services.Customers.Data; namespace Services.Products.Data.Migrations { [DbContext(typeof(ProductDBContext))] [Migration("20190308212022_initial")] partial class initial { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) .HasAnnotation("ProductVersion", "2.2.1-servicing-10028") .HasAnnotation("Relational:MaxIdentifierLength", 63); modelBuilder.Entity("Services.Products.Data.Product", b => { b.Property("Id") .ValueGeneratedOnAdd(); b.Property("Name"); b.Property("Price"); b.Property("Quantity"); b.ToTable("Product"); }); #pragma warning restore 612, 618 } } } ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/Data/Migrations/20190308212022_initial.cs ================================================ using System; using Microsoft.EntityFrameworkCore.Migrations; namespace Services.Products.Data.Migrations { public partial class initial : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.CreateTable( name: "Product", columns: table => new { Id = table.Column(nullable: false), Name = table.Column(nullable: true), Price = table.Column(nullable: false), Quantity = table.Column(nullable: false) }, constraints: table => { table.PrimaryKey("PK_Product", x => x.Id); }); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropTable( name: "Product"); } } } ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/Data/Migrations/20190308222331_base_entity.Designer.cs ================================================ // using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Migrations; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; using Services.Customers.Data; namespace Services.Products.Data.Migrations { [DbContext(typeof(ProductDBContext))] [Migration("20190308222331_base_entity")] partial class base_entity { protected override void BuildTargetModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) .HasAnnotation("ProductVersion", "2.2.1-servicing-10028") .HasAnnotation("Relational:MaxIdentifierLength", 63); modelBuilder.Entity("Services.Products.Data.Product", b => { b.Property("Id") .ValueGeneratedOnAdd(); b.Property("Name"); b.Property("Price"); b.Property("Quantity"); b.Property("UpdateDate"); b.ToTable("Product"); }); #pragma warning restore 612, 618 } } } ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/Data/Migrations/20190308222331_base_entity.cs ================================================ using System; using Microsoft.EntityFrameworkCore.Migrations; namespace Services.Products.Data.Migrations { public partial class base_entity : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.AddColumn( name: "UpdateDate", table: "Product", nullable: false, defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified)); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropColumn( name: "UpdateDate", table: "Product"); } } } ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/Data/Migrations/ProductDBContextModelSnapshot.cs ================================================ // using System; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; using Services.Customers.Data; namespace Services.Products.Data.Migrations { [DbContext(typeof(ProductDBContext))] partial class ProductDBContextModelSnapshot : ModelSnapshot { protected override void BuildModel(ModelBuilder modelBuilder) { #pragma warning disable 612, 618 modelBuilder .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.SerialColumn) .HasAnnotation("ProductVersion", "2.2.1-servicing-10028") .HasAnnotation("Relational:MaxIdentifierLength", 63); modelBuilder.Entity("Services.Products.Data.Product", b => { b.Property("Id") .ValueGeneratedOnAdd(); b.Property("Name"); b.Property("Price"); b.Property("Quantity"); b.Property("UpdateDate"); b.ToTable("Product"); }); #pragma warning restore 612, 618 } } } ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/Data/ProductDBContext.cs ================================================ using Microsoft.EntityFrameworkCore; using Services.Products.Data; namespace Services.Customers.Data { public class ProductDBContext : DbContext { public ProductDBContext(DbContextOptions options) : base(options) { } public DbSet Products { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().ToTable("Product"); } } } ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/Data/SeedData.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Microsoft.Extensions.DependencyInjection; using Services.Customers.Data; using Services.Products.Data; namespace Services.Products.Data { public static class SeedData { public static void Initialize(IServiceProvider serviceProvider) { var context = serviceProvider.GetRequiredService(); context.Database.EnsureCreated(); if (!context.Products.Any()) { var products = new List() { new Product { Name = "Printer", Price = 200, Quantity = 500 }, new Product() { Name = "Mouse", Price = 20, Quantity = 500 }, new Product() { Name = "Keyboard", Price = 50, Quantity = 500 } }; context.Products.AddRange(products); context.SaveChanges(); } } } } ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/Events/OrderCreated.cs ================================================ using System; using System.Collections.Generic; using Shared.Messages; using Newtonsoft.Json; namespace Services.Products.Events { public class OrderCreated : IEvent { public Guid Id { get; } public IDictionary Products { get; } // id, quantity public OrderCreated(Guid id, IDictionary products) { Id = id; Products = products; } } } ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/Events/ProductsReserveFailed.cs ================================================ using System; using Shared.Messages; namespace Services.Products.Events { [MessageNamespace("orders")] public class ProductsReserveFailed : IEvent { public Guid OrderId { get; set; } public ProductsReserveFailed(Guid orderId) { this.OrderId = orderId; } } } ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/Events/ProductsReserved.cs ================================================ using System; using System.Collections.Generic; using Shared.Messages; namespace Services.Products.Events { [MessageNamespace("orders")] public class ProductsReserved : IEvent { public Guid OrderId { get; set; } public IDictionary Products { get; set; } public ProductsReserved(Guid orderId, IDictionary products) { OrderId = orderId; Products = products; } } } ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/Handlers/OrderCreatedHandler.cs ================================================ using System; using System.Threading.Tasks; using Shared.MessageHandlers; using Shared.RabbitMq; using Microsoft.Extensions.Logging; using Services.Customers.Data; using Services.Products.Events; namespace Services.Products.Handlers { public class OrderCreatedHandler : IEventHandler { private readonly ProductDBContext _dbContext; private readonly IBusPublisher _busPublisher; private readonly ILogger _logger; public OrderCreatedHandler(ProductDBContext dbContext, IBusPublisher busPublisher, ILogger logger) { _logger = logger; _dbContext = dbContext; _busPublisher = busPublisher; } public async Task HandleAsync(OrderCreated _event, ICorrelationContext context) { var isReserved = true; foreach ((Guid productId, int quantity) in _event.Products) { var product = await _dbContext.Products.FindAsync(productId); if (product == null) { isReserved = false; _logger.LogInformation($"[Local Transaction] : Product '{productId}' not found. CorrelationId: {context.CorrelationId}"); break; } if (quantity > product.Quantity) { isReserved = false; _logger.LogInformation($"[Local Transaction] : Not available {product.Quantity} {product.Name}. CorrelationId: {context.CorrelationId}"); break; } else { product.Quantity -= quantity; product.UpdateDate = DateTime.Now; } } if (isReserved) { await _dbContext.SaveChangesAsync(); _logger.LogInformation($"[Local Transaction] : Products reserved. CorrelationId: {context.CorrelationId}"); await _busPublisher.PublishAsync(new ProductsReserved(_event.Id, _event.Products), context); } else { _logger.LogInformation($"[Local Transaction] : Products could not be reserved. CorrelationId: {context.CorrelationId}"); await _busPublisher.PublishAsync(new ProductsReserveFailed(_event.Id), context); } } } } ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/Program.cs ================================================ using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Shared.Logging; namespace Services.Products { public class Program { public static void Main(string[] args) { CreateWebHostBuilder(args).Build().Run(); } public static IWebHostBuilder CreateWebHostBuilder(string[] args) => WebHost.CreateDefaultBuilder(args) .UseStartup() .UseLogging("Product.Service"); } } ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/Properties/launchSettings.json ================================================ { "$schema": "http://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:29904", "sslPort": 44329 } }, "profiles": { "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } }, "Services.Products": { "commandName": "Project", "launchBrowser": true, "applicationUrl": "http://localhost:5010", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } } } } ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/Services.Products.csproj ================================================ netcoreapp2.2 InProcess ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/Startup.cs ================================================ using System; using Shared; using Shared.RabbitMq; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Services.Customers.Data; using Services.Products.Data; using Services.Products.Events; namespace Services.Products { public class Startup { public Startup(IConfiguration configuration) { Configuration = configuration; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddDbContext(options => options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")) ); services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); return services.BuildContainer(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseMvc(); app.UseRabbitMq() .SubscribeEvent(); SeedData.Initialize(app.ApplicationServices); } } } ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/appsettings.Development.json ================================================ { "Logging": { "LogLevel": { "Default": "Debug", "System": "Information", "Microsoft": "Information" } } } ================================================ FILE: DotnetIst.Services.Products/src/Services.Products/appsettings.json ================================================ { "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnection": "Host=localhost;Port=5432;Username=postgres;Password=postgres;Database=Products;" }, "rabbitMq": { "namespace": "products", "retries": 3, "retryInterval": 2, "username": "guest", "password": "guest", "virtualHost": "/", "port": 5672, "hostnames": [ "localhost" ], "requestTimeout": "00:00:10", "publishConfirmTimeout": "00:00:01", "recoveryInterval": "00:00:10", "persistentDeliveryMode": true, "autoCloseConnection": true, "automaticRecovery": true, "topologyRecovery": true, "exchange": { "durable": true, "autoDelete": false, "type": "Topic" }, "queue": { "autoDelete": false, "durable": true, "exclusive": false } } } ================================================ FILE: DotnetIst.Shared/src/Shared/Extensions.cs ================================================ using System; using System.Linq; using System.Reflection; using Autofac; using Autofac.Extensions.DependencyInjection; using Shared.RabbitMq; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; namespace Shared { public static class Extensions { public static string Underscore(this string value) => string.Concat(value.Select((x, i) => i > 0 && char.IsUpper(x) ? "_" + x.ToString() : x.ToString())); public static TModel GetOptions(this IConfiguration configuration, string section) where TModel : new() { var model = new TModel(); configuration.GetSection(section).Bind(model); return model; } public static IServiceProvider BuildContainer(this IServiceCollection services) { var builder = new ContainerBuilder(); builder.RegisterAssemblyTypes(Assembly.GetCallingAssembly()) .AsImplementedInterfaces(); builder.Populate(services); builder.AddRabbitMq(); return new AutofacServiceProvider(builder.Build()); } } } ================================================ FILE: DotnetIst.Shared/src/Shared/Logging/LoggingExtensions.cs ================================================ using System; using Microsoft.AspNetCore.Hosting; using Serilog; using Serilog.Sinks.Elasticsearch; namespace Shared.Logging { public static class Extensions { public static IWebHostBuilder UseLogging(this IWebHostBuilder webHostBuilder, string applicationName) { return webHostBuilder.UseSerilog((context, loggerConfiguration) => { loggerConfiguration.Enrich.FromLogContext() .Enrich.WithProperty("Environment", context.HostingEnvironment.EnvironmentName) .Enrich.WithProperty("ApplicationName", applicationName); loggerConfiguration .WriteTo.Console() .WriteTo.Elasticsearch(new ElasticsearchSinkOptions(new Uri("http://localhost:9200/")) { AutoRegisterTemplate = true, IndexFormat = "logstash-{0:yyyy.MM.dd}" }); }); } } } ================================================ FILE: DotnetIst.Shared/src/Shared/MessageHandlers/ICommandHandler.cs ================================================ using System.Threading.Tasks; using Shared.Messages; using Shared.RabbitMq; namespace Shared.MessageHandlers { public interface ICommandHandler where TCommand : ICommand { Task HandleAsync(TCommand command, ICorrelationContext context); } } ================================================ FILE: DotnetIst.Shared/src/Shared/MessageHandlers/IEventHandler.cs ================================================ using System.Threading.Tasks; using Shared.Messages; using Shared.RabbitMq; namespace Shared.MessageHandlers { public interface IEventHandler where TEvent : IEvent { Task HandleAsync(TEvent _event, ICorrelationContext context); } } ================================================ FILE: DotnetIst.Shared/src/Shared/Messages/ICommand.cs ================================================ namespace Shared.Messages { public interface ICommand { } } ================================================ FILE: DotnetIst.Shared/src/Shared/Messages/IEvent.cs ================================================ namespace Shared.Messages { public interface IEvent { } } ================================================ FILE: DotnetIst.Shared/src/Shared/Messages/MessageNamespaceAttribute.cs ================================================ using System; namespace Shared.Messages { public class MessageNamespaceAttribute : Attribute { public string Namespace { get; } public MessageNamespaceAttribute(string _namespace) { Namespace = _namespace?.ToLowerInvariant(); } } } ================================================ FILE: DotnetIst.Shared/src/Shared/Models/BaseEntity.cs ================================================ using System; namespace Shared.Models { public class BaseEntity { public Guid Id { get; set; } public DateTime CreateDate { get; set; } public DateTime? UpdateDate { get; set; } public BaseEntity() { this.CreateDate = DateTime.Now; } } } ================================================ FILE: DotnetIst.Shared/src/Shared/Models/HttpServiceOptions.cs ================================================ namespace Shared.Models { public class HttpServiceOptions { public string ProductHttpServiceUrl { get; set; } public string CustomerHttpServiceUrl { get; set; } } } ================================================ FILE: DotnetIst.Shared/src/Shared/RabbitMq/BusPublisher.cs ================================================ using System.Reflection; using System.Threading.Tasks; using Shared.Messages; using Microsoft.Extensions.Logging; using RawRabbit; using RawRabbit.Enrichers.MessageContext; namespace Shared.RabbitMq { public class BusPublisher : IBusPublisher { private readonly IBusClient _busClient; private readonly ILogger _logger; private readonly string _defaultNamespace; public BusPublisher(IBusClient busClient, RabbitMqOptions options, ILogger logger) { _busClient = busClient; _logger = logger; _defaultNamespace = options.Namespace; } public async Task SendAsync(TCommand command, ICorrelationContext context) where TCommand : ICommand { var commandName = command.GetType().Name; _logger.LogInformation($"[Sent a command] : '{commandName}' Correlation id: '{context.CorrelationId}'"); await _busClient.PublishAsync(command, ctx => ctx.UseMessageContext(context) .UsePublishConfiguration(p => p.WithRoutingKey(GetRoutingKey(command)))); } public async Task PublishAsync(TEvent _event, ICorrelationContext context) where TEvent : IEvent { var eventName = _event.GetType().Name; _logger.LogInformation($"[Published an event] : '{eventName}' Correlation id: '{context.CorrelationId}'"); await _busClient.PublishAsync(_event, ctx => ctx.UseMessageContext(context) .UsePublishConfiguration(p => p.WithRoutingKey(GetRoutingKey(_event)))); } private string GetRoutingKey(T message) { var _namespace = message.GetType().GetCustomAttribute()?.Namespace ?? _defaultNamespace; _namespace = string.IsNullOrWhiteSpace(_namespace) ? string.Empty : $"{_namespace}."; return $"{_namespace}{typeof(T).Name.Underscore()}".ToLowerInvariant(); } } } ================================================ FILE: DotnetIst.Shared/src/Shared/RabbitMq/BusSubscriber.cs ================================================ using System; using System.Reflection; using System.Threading.Tasks; using Shared.MessageHandlers; using Shared.Messages; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Polly; using RawRabbit; using RawRabbit.Common; namespace Shared.RabbitMq { public class BusSubscriber : IBusSubscriber { private readonly ILogger _logger; private readonly IBusClient _busClient; private readonly IServiceProvider _serviceProvider; private readonly string _defaultNamespace; private readonly int _retries; private readonly int _retryInterval; public BusSubscriber(IApplicationBuilder app) { _logger = app.ApplicationServices.GetService>(); _serviceProvider = app.ApplicationServices.GetService(); _busClient = _serviceProvider.GetService(); var options = _serviceProvider.GetService(); _defaultNamespace = options.Namespace; _retries = options.Retries >= 0 ? options.Retries : 3; _retryInterval = options.RetryInterval > 0 ? options.RetryInterval : 2; } public IBusSubscriber SubscribeCommand(string _namespace = null) where TCommand : ICommand { _busClient.SubscribeAsync(async (command, correlationContext) => { var commandHandler = _serviceProvider.GetService>(); return await TryHandleAsync(command, correlationContext, () => commandHandler.HandleAsync(command, correlationContext)); }, ctx => ctx.UseSubscribeConfiguration(cfg => cfg.FromDeclaredQueue(q => q.WithName(GetQueueName(_namespace))))); return this; } public IBusSubscriber SubscribeEvent(string _namespace = null) where TEvent : IEvent { _busClient.SubscribeAsync(async (_event, correlationContext) => { var eventHandler = _serviceProvider.GetService>(); return await TryHandleAsync(_event, correlationContext, () => eventHandler.HandleAsync(_event, correlationContext)); }, ctx => ctx.UseSubscribeConfiguration(cfg => cfg.FromDeclaredQueue(q => q.WithName(GetQueueName(_namespace))))); return this; } private async Task TryHandleAsync(TMessage message, CorrelationContext correlationContext, Func handle) { var currentRetry = 0; var retryPolicy = Policy .Handle() .WaitAndRetryAsync(_retries, i => TimeSpan.FromSeconds(_retryInterval)); var messageName = message.GetType().Name; return await retryPolicy.ExecuteAsync(async () => { var retryMessage = currentRetry == 0 ? string.Empty : $"Retry: {currentRetry}'."; var messageType = message is IEvent ? "n event" : " command"; _logger.LogInformation($"[Handled a{messageType}] : '{messageName}' " + $"Correlation id: '{correlationContext.CorrelationId}'. {retryMessage}"); await handle(); return new Ack(); }); } private string GetQueueName(string _namespace = null) { _namespace = string.IsNullOrWhiteSpace(_namespace) ? (string.IsNullOrWhiteSpace(_defaultNamespace) ? string.Empty : _defaultNamespace) : _namespace; var separatedNamespace = string.IsNullOrWhiteSpace(_namespace) ? string.Empty : $"{_namespace}."; return $"{Assembly.GetEntryAssembly().GetName().Name}/{separatedNamespace}{typeof(T).Name.Underscore()}"; } } } ================================================ FILE: DotnetIst.Shared/src/Shared/RabbitMq/CorrelationContext.cs ================================================ using System; using Newtonsoft.Json; namespace Shared.RabbitMq { public class CorrelationContext : ICorrelationContext { public Guid CorrelationId { get; } public Guid CustomerId { get; } public CorrelationContext() { } [JsonConstructor] private CorrelationContext(Guid correlationId, Guid customerId) { CorrelationId = correlationId; CustomerId = customerId; } public static ICorrelationContext Create(Guid id, Guid customerId) { return new CorrelationContext(id, customerId); } } } ================================================ FILE: DotnetIst.Shared/src/Shared/RabbitMq/IBusPublisher.cs ================================================ using System.Threading.Tasks; using Shared.Messages; namespace Shared.RabbitMq { public interface IBusPublisher { Task SendAsync(TCommand command, ICorrelationContext context) where TCommand : ICommand; Task PublishAsync(TEvent _event, ICorrelationContext context) where TEvent : IEvent; } } ================================================ FILE: DotnetIst.Shared/src/Shared/RabbitMq/IBusSubscriber.cs ================================================ using System; using Shared.Messages; namespace Shared.RabbitMq { public interface IBusSubscriber { IBusSubscriber SubscribeCommand(string _namespace = null) where TCommand : ICommand; IBusSubscriber SubscribeEvent(string _namespace = null) where TEvent : IEvent; } } ================================================ FILE: DotnetIst.Shared/src/Shared/RabbitMq/ICorrelationContext.cs ================================================ using System; namespace Shared.RabbitMq { public interface ICorrelationContext { Guid CorrelationId { get; } Guid CustomerId { get; } } } ================================================ FILE: DotnetIst.Shared/src/Shared/RabbitMq/RabbitMqExtensions.cs ================================================ using System.Reflection; using Autofac; using Shared.MessageHandlers; using Shared.Messages; using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using RawRabbit; using RawRabbit.Common; using RawRabbit.Configuration; using RawRabbit.Enrichers.MessageContext; using RawRabbit.Instantiation; namespace Shared.RabbitMq { public static class RabbitMqExtensions { public static IBusSubscriber UseRabbitMq(this IApplicationBuilder app) => new BusSubscriber(app); public static void AddRabbitMq(this ContainerBuilder builder) { builder.Register(context => { var configuration = context.Resolve(); var options = configuration.GetOptions("rabbitMq"); return options; }).SingleInstance(); builder.Register(context => { var configuration = context.Resolve(); var options = configuration.GetOptions("rabbitMq"); return options; }).SingleInstance(); var assembly = Assembly.GetCallingAssembly(); builder.RegisterAssemblyTypes(assembly) .AsClosedTypesOf(typeof(IEventHandler<>)) .InstancePerDependency(); builder.RegisterAssemblyTypes(assembly) .AsClosedTypesOf(typeof(ICommandHandler<>)) .InstancePerDependency(); builder.RegisterType().As() .InstancePerDependency(); ConfigureBus(builder); } private static void ConfigureBus(ContainerBuilder builder) { builder.Register(context => { var options = context.Resolve(); var configuration = context.Resolve(); var namingConventions = new CustomNamingConventions(options.Namespace); return RawRabbitFactory.CreateInstanceFactory(new RawRabbitOptions { DependencyInjection = ioc => { ioc.AddSingleton(options); ioc.AddSingleton(configuration); ioc.AddSingleton(namingConventions); }, Plugins = p => p .UseAttributeRouting() .UseRetryLater() .UseMessageContext() .UseContextForwarding() }); }).SingleInstance(); builder.Register(context => context.Resolve().Create()); } private class CustomNamingConventions : NamingConventions { public CustomNamingConventions(string defaultNamespace) { ExchangeNamingConvention = type => (type.GetCustomAttribute()?.Namespace ?? defaultNamespace).ToLowerInvariant(); RoutingKeyConvention = type => $"#.{type.GetCustomAttribute()?.Namespace ?? defaultNamespace}.{type.Name.Underscore()}" .ToLowerInvariant(); } } } } ================================================ FILE: DotnetIst.Shared/src/Shared/RabbitMq/RabbitMqOptions.cs ================================================ using RawRabbit.Configuration; namespace Shared.RabbitMq { public class RabbitMqOptions : RawRabbitConfiguration { public string Namespace { get; set; } public int Retries { get; set; } public int RetryInterval { get; set; } } } ================================================ FILE: DotnetIst.Shared/src/Shared/Shared.csproj ================================================ netstandard2.0 ================================================ FILE: README.md ================================================ Presentation : https://speakerdeck.com/suadev/microservice-architecture-and-implementation-with-asp-dot-net-core The aim of the demo is showing ***event-driven and eventual consistent communication*** between the microservices. ## Prerequities * DotNet Core SDK 2.2 * Docker * pgAdmin or Azure Data Studio (Needs PostgreSQL extension - https://github.com/Microsoft/azuredatastudio-postgresql/ ) ## Running in Debug Mode * Run 'docker-compose up' * Wait all services to up and running. ( rabbitmq, postgres, elasticsearch and kibana ) * Select 'All' debug option and start debuging. * Wait until all microservices are up and running. P.S. You can use ***.postman_project/Dotnet_Istanbul.postman_collection.json*** file for a quick test from Postman. ## Tool Set * Asp.Net Core 2.2 * Entity Framework Core 2.2 * PostgreSQL - Npgsql * Serilog - Elasticsearch - Kibana * RabbitMQ - RawRabbit * Docker Containers ( PostgreSQL, RabbitMQ, Elasticsearch and Kibana ) * pgAdmin or Azure Data Studio * VS Code ================================================ FILE: docker-compose.yml ================================================ version: '3.7' services: rabbitmq: image: rabbitmq:3-management container_name: myrabbitmq ports: - "15672:15672" - "5672:5672" environment: - RABBITMQ_DEFAULT_USER=guest - RABBITMQ_DEFAULT_PASS=guest postgres: image: postgres container_name: mypostgres ports: - "5432:5432" environment: - POSTGRES_PASSWORD=postgres elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:6.6.1 container_name: myelastic environment: - cluster.name=docker-cluster - bootstrap.memory_lock=true - "ES_JAVA_OPTS=-Xms512m -Xmx512m" ulimits: memlock: soft: -1 hard: -1 ports: - "9200:9200" - "9300:9300" kibana: image: docker.elastic.co/kibana/kibana:6.6.1 container_name: mykibana ports: - "5601:5601" ================================================ FILE: dotnet-istanbul-microservices-demo.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.26124.0 MinimumVisualStudioVersion = 15.0.26124.0 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DotnetIst.Api", "DotnetIst.Api", "{674A9DD4-F9DA-4F9F-81E3-AB6A24C5149D}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{CA1A4673-F703-4C40-B44E-AE28673B4A71}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Api", "DotnetIst.Api\src\Api\Api.csproj", "{2BCBD3FB-715D-45A5-96DB-346608043045}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DotnetIst.Shared", "DotnetIst.Shared", "{A14724F1-AE75-45BE-AFF2-F46603C797DE}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{7FC87A4C-7F67-49D8-A702-9CC75F9910FB}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Shared", "DotnetIst.Shared\src\Shared\Shared.csproj", "{DCF6A908-B7C2-412C-924D-A598647F7540}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DotnetIst.Services.Customers", "DotnetIst.Services.Customers", "{6806424F-4261-4347-8CDE-5F6399BBC6BF}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{EA331B36-EF9B-4C04-B52F-0192A629A229}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Services.Customers", "DotnetIst.Services.Customers\src\Services.Customers\Services.Customers.csproj", "{FA617103-F493-4FDD-96FA-0E83B881E0B0}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DotnetIst.Services.Notifications", "DotnetIst.Services.Notifications", "{3B87C5C6-314D-4101-B233-A196403845AB}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{84EC4F28-DC06-4F42-90B1-F7C8D57B6C1B}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Services.Notifications", "DotnetIst.Services.Notifications\src\Services.Notifications\Services.Notifications.csproj", "{577857E0-19A7-406C-8495-7BCF71886F71}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DotnetIst.Services.Orders", "DotnetIst.Services.Orders", "{8E520BD8-B6D7-4205-93FC-7F9BFA99A4A7}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{28BA3B30-1F93-4907-B6BB-32D09A038CC9}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Services.Orders", "DotnetIst.Services.Orders\src\Services.Orders\Services.Orders.csproj", "{3AF46C68-DA51-4CFE-B9D1-BA247AF2C9C0}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DotnetIst.Services.Products", "DotnetIst.Services.Products", "{020A54F8-7DC3-4148-8797-7534A89B32F7}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{B5E52D90-0C27-4D1C-9DCE-B614658969FA}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Services.Products", "DotnetIst.Services.Products\src\Services.Products\Services.Products.csproj", "{83E22247-7E48-429B-83B7-979263473F36}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {2BCBD3FB-715D-45A5-96DB-346608043045}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2BCBD3FB-715D-45A5-96DB-346608043045}.Debug|Any CPU.Build.0 = Debug|Any CPU {2BCBD3FB-715D-45A5-96DB-346608043045}.Debug|x64.ActiveCfg = Debug|Any CPU {2BCBD3FB-715D-45A5-96DB-346608043045}.Debug|x64.Build.0 = Debug|Any CPU {2BCBD3FB-715D-45A5-96DB-346608043045}.Debug|x86.ActiveCfg = Debug|Any CPU {2BCBD3FB-715D-45A5-96DB-346608043045}.Debug|x86.Build.0 = Debug|Any CPU {2BCBD3FB-715D-45A5-96DB-346608043045}.Release|Any CPU.ActiveCfg = Release|Any CPU {2BCBD3FB-715D-45A5-96DB-346608043045}.Release|Any CPU.Build.0 = Release|Any CPU {2BCBD3FB-715D-45A5-96DB-346608043045}.Release|x64.ActiveCfg = Release|Any CPU {2BCBD3FB-715D-45A5-96DB-346608043045}.Release|x64.Build.0 = Release|Any CPU {2BCBD3FB-715D-45A5-96DB-346608043045}.Release|x86.ActiveCfg = Release|Any CPU {2BCBD3FB-715D-45A5-96DB-346608043045}.Release|x86.Build.0 = Release|Any CPU {DCF6A908-B7C2-412C-924D-A598647F7540}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DCF6A908-B7C2-412C-924D-A598647F7540}.Debug|Any CPU.Build.0 = Debug|Any CPU {DCF6A908-B7C2-412C-924D-A598647F7540}.Debug|x64.ActiveCfg = Debug|Any CPU {DCF6A908-B7C2-412C-924D-A598647F7540}.Debug|x64.Build.0 = Debug|Any CPU {DCF6A908-B7C2-412C-924D-A598647F7540}.Debug|x86.ActiveCfg = Debug|Any CPU {DCF6A908-B7C2-412C-924D-A598647F7540}.Debug|x86.Build.0 = Debug|Any CPU {DCF6A908-B7C2-412C-924D-A598647F7540}.Release|Any CPU.ActiveCfg = Release|Any CPU {DCF6A908-B7C2-412C-924D-A598647F7540}.Release|Any CPU.Build.0 = Release|Any CPU {DCF6A908-B7C2-412C-924D-A598647F7540}.Release|x64.ActiveCfg = Release|Any CPU {DCF6A908-B7C2-412C-924D-A598647F7540}.Release|x64.Build.0 = Release|Any CPU {DCF6A908-B7C2-412C-924D-A598647F7540}.Release|x86.ActiveCfg = Release|Any CPU {DCF6A908-B7C2-412C-924D-A598647F7540}.Release|x86.Build.0 = Release|Any CPU {FA617103-F493-4FDD-96FA-0E83B881E0B0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FA617103-F493-4FDD-96FA-0E83B881E0B0}.Debug|Any CPU.Build.0 = Debug|Any CPU {FA617103-F493-4FDD-96FA-0E83B881E0B0}.Debug|x64.ActiveCfg = Debug|Any CPU {FA617103-F493-4FDD-96FA-0E83B881E0B0}.Debug|x64.Build.0 = Debug|Any CPU {FA617103-F493-4FDD-96FA-0E83B881E0B0}.Debug|x86.ActiveCfg = Debug|Any CPU {FA617103-F493-4FDD-96FA-0E83B881E0B0}.Debug|x86.Build.0 = Debug|Any CPU {FA617103-F493-4FDD-96FA-0E83B881E0B0}.Release|Any CPU.ActiveCfg = Release|Any CPU {FA617103-F493-4FDD-96FA-0E83B881E0B0}.Release|Any CPU.Build.0 = Release|Any CPU {FA617103-F493-4FDD-96FA-0E83B881E0B0}.Release|x64.ActiveCfg = Release|Any CPU {FA617103-F493-4FDD-96FA-0E83B881E0B0}.Release|x64.Build.0 = Release|Any CPU {FA617103-F493-4FDD-96FA-0E83B881E0B0}.Release|x86.ActiveCfg = Release|Any CPU {FA617103-F493-4FDD-96FA-0E83B881E0B0}.Release|x86.Build.0 = Release|Any CPU {577857E0-19A7-406C-8495-7BCF71886F71}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {577857E0-19A7-406C-8495-7BCF71886F71}.Debug|Any CPU.Build.0 = Debug|Any CPU {577857E0-19A7-406C-8495-7BCF71886F71}.Debug|x64.ActiveCfg = Debug|Any CPU {577857E0-19A7-406C-8495-7BCF71886F71}.Debug|x64.Build.0 = Debug|Any CPU {577857E0-19A7-406C-8495-7BCF71886F71}.Debug|x86.ActiveCfg = Debug|Any CPU {577857E0-19A7-406C-8495-7BCF71886F71}.Debug|x86.Build.0 = Debug|Any CPU {577857E0-19A7-406C-8495-7BCF71886F71}.Release|Any CPU.ActiveCfg = Release|Any CPU {577857E0-19A7-406C-8495-7BCF71886F71}.Release|Any CPU.Build.0 = Release|Any CPU {577857E0-19A7-406C-8495-7BCF71886F71}.Release|x64.ActiveCfg = Release|Any CPU {577857E0-19A7-406C-8495-7BCF71886F71}.Release|x64.Build.0 = Release|Any CPU {577857E0-19A7-406C-8495-7BCF71886F71}.Release|x86.ActiveCfg = Release|Any CPU {577857E0-19A7-406C-8495-7BCF71886F71}.Release|x86.Build.0 = Release|Any CPU {3AF46C68-DA51-4CFE-B9D1-BA247AF2C9C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3AF46C68-DA51-4CFE-B9D1-BA247AF2C9C0}.Debug|Any CPU.Build.0 = Debug|Any CPU {3AF46C68-DA51-4CFE-B9D1-BA247AF2C9C0}.Debug|x64.ActiveCfg = Debug|Any CPU {3AF46C68-DA51-4CFE-B9D1-BA247AF2C9C0}.Debug|x64.Build.0 = Debug|Any CPU {3AF46C68-DA51-4CFE-B9D1-BA247AF2C9C0}.Debug|x86.ActiveCfg = Debug|Any CPU {3AF46C68-DA51-4CFE-B9D1-BA247AF2C9C0}.Debug|x86.Build.0 = Debug|Any CPU {3AF46C68-DA51-4CFE-B9D1-BA247AF2C9C0}.Release|Any CPU.ActiveCfg = Release|Any CPU {3AF46C68-DA51-4CFE-B9D1-BA247AF2C9C0}.Release|Any CPU.Build.0 = Release|Any CPU {3AF46C68-DA51-4CFE-B9D1-BA247AF2C9C0}.Release|x64.ActiveCfg = Release|Any CPU {3AF46C68-DA51-4CFE-B9D1-BA247AF2C9C0}.Release|x64.Build.0 = Release|Any CPU {3AF46C68-DA51-4CFE-B9D1-BA247AF2C9C0}.Release|x86.ActiveCfg = Release|Any CPU {3AF46C68-DA51-4CFE-B9D1-BA247AF2C9C0}.Release|x86.Build.0 = Release|Any CPU {83E22247-7E48-429B-83B7-979263473F36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {83E22247-7E48-429B-83B7-979263473F36}.Debug|Any CPU.Build.0 = Debug|Any CPU {83E22247-7E48-429B-83B7-979263473F36}.Debug|x64.ActiveCfg = Debug|Any CPU {83E22247-7E48-429B-83B7-979263473F36}.Debug|x64.Build.0 = Debug|Any CPU {83E22247-7E48-429B-83B7-979263473F36}.Debug|x86.ActiveCfg = Debug|Any CPU {83E22247-7E48-429B-83B7-979263473F36}.Debug|x86.Build.0 = Debug|Any CPU {83E22247-7E48-429B-83B7-979263473F36}.Release|Any CPU.ActiveCfg = Release|Any CPU {83E22247-7E48-429B-83B7-979263473F36}.Release|Any CPU.Build.0 = Release|Any CPU {83E22247-7E48-429B-83B7-979263473F36}.Release|x64.ActiveCfg = Release|Any CPU {83E22247-7E48-429B-83B7-979263473F36}.Release|x64.Build.0 = Release|Any CPU {83E22247-7E48-429B-83B7-979263473F36}.Release|x86.ActiveCfg = Release|Any CPU {83E22247-7E48-429B-83B7-979263473F36}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {CA1A4673-F703-4C40-B44E-AE28673B4A71} = {674A9DD4-F9DA-4F9F-81E3-AB6A24C5149D} {2BCBD3FB-715D-45A5-96DB-346608043045} = {CA1A4673-F703-4C40-B44E-AE28673B4A71} {7FC87A4C-7F67-49D8-A702-9CC75F9910FB} = {A14724F1-AE75-45BE-AFF2-F46603C797DE} {DCF6A908-B7C2-412C-924D-A598647F7540} = {7FC87A4C-7F67-49D8-A702-9CC75F9910FB} {EA331B36-EF9B-4C04-B52F-0192A629A229} = {6806424F-4261-4347-8CDE-5F6399BBC6BF} {FA617103-F493-4FDD-96FA-0E83B881E0B0} = {EA331B36-EF9B-4C04-B52F-0192A629A229} {84EC4F28-DC06-4F42-90B1-F7C8D57B6C1B} = {3B87C5C6-314D-4101-B233-A196403845AB} {577857E0-19A7-406C-8495-7BCF71886F71} = {84EC4F28-DC06-4F42-90B1-F7C8D57B6C1B} {28BA3B30-1F93-4907-B6BB-32D09A038CC9} = {8E520BD8-B6D7-4205-93FC-7F9BFA99A4A7} {3AF46C68-DA51-4CFE-B9D1-BA247AF2C9C0} = {28BA3B30-1F93-4907-B6BB-32D09A038CC9} {B5E52D90-0C27-4D1C-9DCE-B614658969FA} = {020A54F8-7DC3-4148-8797-7534A89B32F7} {83E22247-7E48-429B-83B7-979263473F36} = {B5E52D90-0C27-4D1C-9DCE-B614658969FA} EndGlobalSection EndGlobal