Modern Architecture Shop - Autoscaler

Modern Architecture Shop is a clean-lightweight .NET Microservices application. Here I will demonstrate the use of Dapr to build Microservices-based applications.
 
Open Invitation - Any developer is welcome to join our team! Just send me a request.
 

The application UI

 
Modern Architecture Shop - Autoscaler
 
Modern Architecture Shop - Autoscaler
 
Modern Architecture Shop - Autoscaler
 
The implemented application architect is still based on the classic Microservices architectural style; we have a collection of DDD-services, which are working together to build the system.
 
Modern Architecture Shop - Autoscaler
 
The diagram above is created with Draw.io. Draw.io is a free online diagram software.
 

Roadmap

  1. Finishing the Order and Payment services.
  2. The first challenge is scaling the application out. I give an example which provides Proof of the Concept for containers scaling and testing the system with Chaos Monkey Tests.
  3. Changing the services to Actors with a scaling concept.
  4. Finally, using KEDA. KEDA is a Kubernetes-based Event-Driven Autoscaler (Horizontal Pod Autoscaler)
Modern Architecture Shop on GitHub - ModernArchitectureShop
  • In this version, I have done more clean architecture and clean code stuff:
  • I have separated the application from the Infrastructure.
  • Use Cases are moved into the application.
  • The Persistence abstraction moved to the application.
In the example below, you can see that the Store application contains the business logic abstraction.
 
Modern Architecture Shop - Autoscaler
 
The framework's references moved to the infrastructure assembly. If you remember in a previous article, our main goal was to make the infrastructure depending on the application.
 
In addition, I have added the order of domain events. These events are working together on the use cases to build a single coherent system.
 
ProcessOrder Event
 
The event is fired when the user clicks on the buy button.
 
PayOrder Event
 
This event creates the order so that it can be sent to the Payment Service. After that, the payment can succeed or fail.
 
PrdocutsSold Event
 
The event is fired when the payment is successful. This event helps to update the products availability in the Store.
 
PaymentConfirmed Event
 
The event is fired when the payment has been successful. This event is responsible to remove the processed order and basket information.
 
PaymentFailed Event
 
The event is fired when the payment fails. This event is used to remove the failed order data and to reactivate the buy state in the Basket service.
 

ModernArchitectureShop.ShopUI

 
Modern Architecture Shop - Autoscaler
 
As you see in the image above, I have selected ProductsService and ProductsDaprClient, these two classes are equivalent, both are calling the Store API, but they are using two different approaches, the Product API is using HTTPClient to call the Store API and the other one is using DaprCilent. 
  1. public class ProductsService    
  2. {    
  3.     private readonly HttpClient _storeHttpClient;    
  4.     private readonly IHttpContextAccessor _httpContextAccessor;    
  5.     
  6.     public ProductsService(HttpClient storeHttpClient, IHttpContextAccessor httpContextAccessor)    
  7.     {    
  8.         _storeHttpClient = storeHttpClient ?? throw new ArgumentNullException(nameof(storeHttpClient));    
  9.         _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));    
  10.     }    
  11.     
  12.     public async Task AttachAccessTokenToHeader()    
  13.     {    
  14.         var accessToken = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");    
  15.         if (accessToken != null)    
  16.         {    
  17.             var auth = _storeHttpClient.DefaultRequestHeaders.Authorization?.Parameter;    
  18.             if (auth == null)    
  19.                 _storeHttpClient.DefaultRequestHeaders.Add("Authorization""Bearer " + accessToken);    
  20.         }    
  21.     }    
  22.     
  23.     public async Task<ServiceResult<string>> SearchProducts(string url)    
  24.     {    
  25.         await AttachAccessTokenToHeader();    
  26.     
  27.         HttpResponseMessage response;    
  28.         try    
  29.         {    
  30.             response = await _storeHttpClient.GetAsync(url);    
  31.             response.EnsureSuccessStatusCode();    
  32.         }    
  33.         catch (HttpRequestException e)    
  34.         {    
  35.             return new ServiceResult<string>    
  36.             {    
  37.                 Content = null!,    
  38.                 StatusCode = 500, // Server Error!    
  39.                 Error = e.Message    
  40.             };    
  41.         }    
  42.     
  43.         return new ServiceResult<string>    
  44.         {    
  45.             Content = await response.Content.ReadAsStringAsync(),    
  46.             StatusCode = (int)response.StatusCode,    
  47.             Error = string.Empty    
  48.         };    
  49.     
  50.     }    
  51.     
  52.     public async Task<ServiceResult<string>> GetProductsAsync(string url)    
  53.     {    
  54.         await AttachAccessTokenToHeader();    
  55.     
  56.         HttpResponseMessage response;    
  57.         try    
  58.         {    
  59.             response = await _storeHttpClient.GetAsync(url);    
  60.             response.EnsureSuccessStatusCode();    
  61.         }    
  62.         catch (HttpRequestException e)    
  63.         {    
  64.             return new ServiceResult<string>    
  65.             {    
  66.                 Content = null!,    
  67.                 StatusCode = 500, // Server Error!    
  68.                 Error = e.Message    
  69.             };    
  70.         }    
  71.     
  72.         return new ServiceResult<string>    
  73.         {    
  74.             Content =  await response.Content.ReadAsStringAsync(),    
  75.             StatusCode = (int)response.StatusCode,    
  76.             Error = string.Empty    
  77.         };    
  78.     }    
  79. }    
  1. public class ProductsDaprClient    
  2. {    
  3.     private readonly DaprClient _daprClient;    
  4.     
  5.     public ProductsDaprClient(DaprClient daprClient)    
  6.     {    
  7.         _daprClient = daprClient;    
  8.     }    
  9.     
  10.     public async Task<GetProductsResponse> GetProductsAsync(    
  11.         string url,    
  12.         GetProductsCommand getProductsCommand,    
  13.         CancellationToken cancellationToken)    
  14.     {    
  15.         return await this._daprClient.    
  16.                   InvokeMethodAsync<GetProductsCommand, GetProductsResponse> ("storeapi",  url, getProductsCommand,    
  17.                   new HTTPExtension { Verb = HTTPVerb.Get },    
  18.                   cancellationToken);    
  19.     }    
  20.     
  21.     public async Task<GetProductsResponse> SearchProductsAsync(    
  22.         string url,    
  23.         SearchProductsCommand searchProductsCommand,    
  24.         CancellationToken cancellationToken)    
  25.     {    
  26.         return await this._daprClient.    
  27.             InvokeMethodAsync<SearchProductsCommand, GetProductsResponse>    
  28.             ("storeapi",    
  29.                 url,    
  30.                 searchProductsCommand,    
  31.                 new HTTPExtension { Verb = HTTPVerb.Get },    
  32.                 cancellationToken);    
  33.     }    
  34.     
  35.     public class GetProductsCommand    
  36.     {    
  37.         public int PageIndex { getset; } = 1;    
  38.     
  39.         public int PageSize { getset; } = 10;    
  40.     }    
  41.     
  42.     public class GetProductsResponse    
  43.     {    
  44.         public int TotalOfProducts { getset; }    
  45.         public IEnumerable<ProductModel> Products { getset; } = new ProductModel[0];    
  46.     }    
  47.     
  48.     public class SearchProductsCommand    
  49.     {    
  50.         public string Filter { getset; } = string.Empty;    
  51.     
  52.         public int PageIndex { getset; } = 1;    
  53.     
  54.         public int PageSize { getset; } = 10;    
  55.     }    
  56. }    
You can get the products with Dapr as following: 
  1. // Do it with Dapr  
  2. try    
  3. {    
  4.   ProductsDaprClient.GetProductsResponse products =    
  5.     await ProductsDaprClient.GetProductsAsync("api/products",    
  6.       new ProductsDaprClient.GetProductsCommand { PageIndex = Page, PageSize = _pageSize },    
  7.       new CancellationToken());    
  8.     
  9.   _productsModel = new ProductsModel { Products = products.Products.ToList(), TotalOfProducts = products.TotalOfProducts };    
  10. }    
  11. catch (Exception e)    
  12. {    
  13.   // Todo just for Developers!    
  14.   _errorMessage = $"Error: {e.Message}";    
  15.   _productsModel = new ProductsModel(); ;    
  16. }    
  17.     
  18. // Alternatively, do it with HTTP classic    
  19. var response = await ProductsService.GetProductsAsync(ProcessUrl());    
  20.     
  21. if (response.StatusCode == (int)System.Net.HttpStatusCode.OK)    
  22. {    
  23.   _productsModel = JsonSerializer    
  24.                                  .Deserialize<ProductsModel>(response.Content,    
  25.                                                             new JsonSerializerOptions { PropertyNameCaseInsensitive = true });    
  26. }    
  27. else    
  28. {    
  29.   _errorMessage = $"Error: {response.Error}";    
  30.   _productsModel = new ProductsModel();    
  31. }   
How can you test the modern shop?
 
Required
First Apporach with Visual Studio
 
Build and Start the Shop
 
Install tye
  1. dotnet tool install -g Microsoft.Tye --version "0.5.0-*" --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json
Start tye-min.yaml in console
  1. tye run tye-min.yaml
Open the solution file “ModernArchitectureShop.sln” with the  latest Visual Studio 2019 preview.
 
Set the Startup projects as shown below
 
Modern Architecture Shop - Autoscaler
 
PRESS F5 and enjoy it!
 
The second apporach runs the shop with Dapr
 
Alternatively, to Visual Studio 2019
 
Install Tye
  1. dotnet tool install -g Microsoft.Tye --version "0.5.0-*" --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json
Start tye-min.yaml in the console
  1. tye run tye-min.yaml
Install Dapr
  1. powershell -Command "iwr -useb https://raw.githubusercontent.com/dapr/cli/master/install/install.ps1 | iex"
Execute dapr_start.ps1 in the PowerShell
  1. ./dapr_start.ps1
Third appoch run it with Tye
 
Tye install
 
This will install the newest available build from our CI.
  1. dotnet tool install -g Microsoft.Tye --version "0.5.0-*" --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json
If you already have a build installed and you want to update, replace install with update
  1. dotnet tool update -g Microsoft.Tye --version "0.5.0-*" --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet5/nuget/v3/index.json
Execute the tye command
  1. tye run

Summary

 
Modern Architecture Shop is a clean-lightweight .NET and scalable application. Keep your eye on the Road Map (watch it on GitHub). The next version will contain a minimal feature set so that the user can add products to the basket and pay it. Iwill provide recommendation service and all other AI services or features  later.