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
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.
The diagram above is created with Draw.io. Draw.io is a free online diagram software.
Roadmap
- Finishing the Order and Payment services.
- 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.
- Changing the services to Actors with a scaling concept.
- Finally, using KEDA. KEDA is a Kubernetes-based Event-Driven Autoscaler (Horizontal Pod Autoscaler)
- 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.
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
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.
- public class ProductsService
- {
- private readonly HttpClient _storeHttpClient;
- private readonly IHttpContextAccessor _httpContextAccessor;
-
- public ProductsService(HttpClient storeHttpClient, IHttpContextAccessor httpContextAccessor)
- {
- _storeHttpClient = storeHttpClient ?? throw new ArgumentNullException(nameof(storeHttpClient));
- _httpContextAccessor = httpContextAccessor ?? throw new ArgumentNullException(nameof(httpContextAccessor));
- }
-
- public async Task AttachAccessTokenToHeader()
- {
- var accessToken = await _httpContextAccessor.HttpContext.GetTokenAsync("access_token");
- if (accessToken != null)
- {
- var auth = _storeHttpClient.DefaultRequestHeaders.Authorization?.Parameter;
- if (auth == null)
- _storeHttpClient.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
- }
- }
-
- public async Task<ServiceResult<string>> SearchProducts(string url)
- {
- await AttachAccessTokenToHeader();
-
- HttpResponseMessage response;
- try
- {
- response = await _storeHttpClient.GetAsync(url);
- response.EnsureSuccessStatusCode();
- }
- catch (HttpRequestException e)
- {
- return new ServiceResult<string>
- {
- Content = null!,
- StatusCode = 500,
- Error = e.Message
- };
- }
-
- return new ServiceResult<string>
- {
- Content = await response.Content.ReadAsStringAsync(),
- StatusCode = (int)response.StatusCode,
- Error = string.Empty
- };
-
- }
-
- public async Task<ServiceResult<string>> GetProductsAsync(string url)
- {
- await AttachAccessTokenToHeader();
-
- HttpResponseMessage response;
- try
- {
- response = await _storeHttpClient.GetAsync(url);
- response.EnsureSuccessStatusCode();
- }
- catch (HttpRequestException e)
- {
- return new ServiceResult<string>
- {
- Content = null!,
- StatusCode = 500,
- Error = e.Message
- };
- }
-
- return new ServiceResult<string>
- {
- Content = await response.Content.ReadAsStringAsync(),
- StatusCode = (int)response.StatusCode,
- Error = string.Empty
- };
- }
- }
- public class ProductsDaprClient
- {
- private readonly DaprClient _daprClient;
-
- public ProductsDaprClient(DaprClient daprClient)
- {
- _daprClient = daprClient;
- }
-
- public async Task<GetProductsResponse> GetProductsAsync(
- string url,
- GetProductsCommand getProductsCommand,
- CancellationToken cancellationToken)
- {
- return await this._daprClient.
- InvokeMethodAsync<GetProductsCommand, GetProductsResponse> ("storeapi", url, getProductsCommand,
- new HTTPExtension { Verb = HTTPVerb.Get },
- cancellationToken);
- }
-
- public async Task<GetProductsResponse> SearchProductsAsync(
- string url,
- SearchProductsCommand searchProductsCommand,
- CancellationToken cancellationToken)
- {
- return await this._daprClient.
- InvokeMethodAsync<SearchProductsCommand, GetProductsResponse>
- ("storeapi",
- url,
- searchProductsCommand,
- new HTTPExtension { Verb = HTTPVerb.Get },
- cancellationToken);
- }
-
- public class GetProductsCommand
- {
- public int PageIndex { get; set; } = 1;
-
- public int PageSize { get; set; } = 10;
- }
-
- public class GetProductsResponse
- {
- public int TotalOfProducts { get; set; }
- public IEnumerable<ProductModel> Products { get; set; } = new ProductModel[0];
- }
-
- public class SearchProductsCommand
- {
- public string Filter { get; set; } = string.Empty;
-
- public int PageIndex { get; set; } = 1;
-
- public int PageSize { get; set; } = 10;
- }
- }
You can get the products with Dapr as following:
-
- try
- {
- ProductsDaprClient.GetProductsResponse products =
- await ProductsDaprClient.GetProductsAsync("api/products",
- new ProductsDaprClient.GetProductsCommand { PageIndex = Page, PageSize = _pageSize },
- new CancellationToken());
-
- _productsModel = new ProductsModel { Products = products.Products.ToList(), TotalOfProducts = products.TotalOfProducts };
- }
- catch (Exception e)
- {
-
- _errorMessage = $"Error: {e.Message}";
- _productsModel = new ProductsModel(); ;
- }
-
-
- var response = await ProductsService.GetProductsAsync(ProcessUrl());
-
- if (response.StatusCode == (int)System.Net.HttpStatusCode.OK)
- {
- _productsModel = JsonSerializer
- .Deserialize<ProductsModel>(response.Content,
- new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
- }
- else
- {
- _errorMessage = $"Error: {response.Error}";
- _productsModel = new ProductsModel();
- }
How can you test the modern shop?
Required
First Apporach with Visual Studio
Build and Start the Shop
Install tye
- dotnet tool install -g Microsoft.Tye --version "0.5.0-*" --add-source https:
Start tye-min.yaml in console
Open the solution file “ModernArchitectureShop.sln” with the latest Visual Studio 2019 preview.
Set the Startup projects as shown below
PRESS F5 and enjoy it!
The second apporach runs the shop with Dapr
Alternatively, to Visual Studio 2019
Install Tye
- dotnet tool install -g Microsoft.Tye --version "0.5.0-*" --add-source https:
Start tye-min.yaml in the console
Install Dapr
- powershell -Command "iwr -useb https://raw.githubusercontent.com/dapr/cli/master/install/install.ps1 | iex"
Execute dapr_start.ps1 in the PowerShell
Third appoch run it with Tye
Tye install
This will install the newest available build from our CI.
- dotnet tool install -g Microsoft.Tye --version "0.5.0-*" --add-source https:
If you already have a build installed and you want to update, replace install with update
- dotnet tool update -g Microsoft.Tye --version "0.5.0-*" --add-source https:
Execute the tye command
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.