Browse Source

Enhance microservice tutorial by adding part-07, focusing on implementing event-driven architecture with message queues and improving service communication

pull/21456/head
ahmetfarukulu 2 years ago
parent
commit
5508b12151
  1. BIN
      docs/en/tutorials/microservice/images/add-catalog-service-contracts-reference.png
  2. BIN
      docs/en/tutorials/microservice/images/add-package-reference-ordering-service.png
  3. BIN
      docs/en/tutorials/microservice/images/generate-catalog-service-proxy.png
  4. BIN
      docs/en/tutorials/microservice/images/generate-proxy-catalog-service.png
  5. 243
      docs/en/tutorials/microservice/part-06.md

BIN
docs/en/tutorials/microservice/images/add-catalog-service-contracts-reference.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
docs/en/tutorials/microservice/images/add-package-reference-ordering-service.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

BIN
docs/en/tutorials/microservice/images/generate-catalog-service-proxy.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
docs/en/tutorials/microservice/images/generate-proxy-catalog-service.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

243
docs/en/tutorials/microservice/part-06.md

@ -16,7 +16,7 @@
In the previous part, we implemented the functionality of the Ordering microservice. However, when listing orders, we need to display the product name instead of the product ID. To achieve this, we must call the Catalog service to retrieve the product name for each order item.
In this section, we will integrate the Ordering microservice with the Catalog service using HTTP API calls.
In this section, we will integrate the Ordering service with the Catalog service using HTTP API calls.
## The Need for the Integration Services
@ -32,7 +32,7 @@ First, we need to create a service that will handle the communication with the C
### Defining the `IProductIntegrationService` Interface
Open the `CloudCrm.CatalogService` .NET solution in your IDE. Locate the `CloudCrm.CatalogService.Contracts` project, and create a new folder named `Integration` within the `Services` folder. Inside this folder, add a new interface named `IProductIntegrationService` with the following code:
Open the `CloudCrm.CatalogService` .NET solution in your IDE. Locate the `CloudCrm.CatalogService.Contracts` project, and create a new folder named `IntegrationServices`. Inside this folder, add a new interface named `IProductIntegrationService` with the following code:
```csharp
using System;
@ -42,7 +42,7 @@ using CloudCrm.CatalogService.Products;
using Volo.Abp;
using Volo.Abp.Application.Services;
namespace CloudCrm.CatalogService.Services.Integration;
namespace CloudCrm.CatalogService.IntegrationServices;
[IntegrationService]
public interface IProductIntegrationService : IApplicationService
@ -51,4 +51,239 @@ public interface IProductIntegrationService : IApplicationService
}
```
`IProductIntegrationService` is very similar to a typical [application service](../../framework/architecture/domain-driven-design/application-services.md). The only difference is that it is marked with the `[IntegrationService]` attribute. This attribute is used to identify the service as an integration service, which allows ABP to handle the communication between services. ABP behave differently for them (for example, ABP doesn't expose [integration services](../../framework/api-development/integration-services.md) as HTTP APIs by default if you've configured the [Auto API Controllers](../../framework/api-development/auto-controllers.md) feature)
`IProductIntegrationService` is very similar to a typical [application service](../../framework/architecture/domain-driven-design/application-services.md). The only difference is that it is marked with the `[IntegrationService]` attribute. This attribute is used to identify the service as an integration service, which allows ABP to handle the communication between services. ABP behave differently for them (for example, ABP doesn't expose [integration services](../../framework/api-development/integration-services.md) as HTTP APIs by default if you've configured the [Auto API Controllers](../../framework/api-development/auto-controllers.md) feature)
`IProductIntegrationService` contains a single method named `GetProductsByIdsAsync`. This method takes a list of product IDs and returns a list of `ProductDto` objects. This is exactly what we need in the Ordering service.
> **Design Tip**
>
> You may think if we can use the existing application services (like `IProductAppService`) from other services instead of creating specific integration services. Technically you can use, ABP has no restriction. However, from good design and best practice points, we don't suggest it. Because, application services are designed to be consumed specifically by the presentation layer. They will have different authorization and validation logic, they will need different DTO input and output properties, they will have different performance, optimization and caching requirements, and so on. And most importantly, all these will change by the time based on UI requirements and these changes may break your integrations later. It is best to implement specific integration APIs that is designed and optimized for that purpose.
>
> We've reused the `ProductDto` object created for `IProductAppService`, which can be reasonable from a maintenance point of view. But if you think your integration service results will be different from the application service results in the future, it can be good to separate them from the first day so you don't need to introduce breaking changes later.
### Implementing the `ProductIntegrationService` Class
Now, let's implement the `IProductIntegrationService` interface. Create a new folder named `IntegrationServices` in the `CloudCrm.CatalogService` project. Inside this folder, add a new class named `ProductIntegrationService` with the following code:
```csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using CloudCrm.CatalogService.Localization;
using CloudCrm.CatalogService.Products;
using Volo.Abp;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace CloudCrm.CatalogService.IntegrationServices;
[IntegrationService]
public class ProductIntegrationService : ApplicationService, IProductIntegrationService
{
private readonly IRepository<Product, Guid> _productRepository;
public ProductIntegrationService(IRepository<Product, Guid> productRepository)
{
LocalizationResource = typeof(CatalogServiceResource);
_productRepository = productRepository;
}
public async Task<List<ProductDto>> GetProductsByIdsAsync(List<Guid> ids)
{
var products = await _productRepository.GetListAsync(
product => ids.Contains(product.Id)
);
return ObjectMapper.Map<List<Product>, List<ProductDto>>(products);
}
}
```
`ProductIntegrationService` is a typical application service class that implements the `IProductIntegrationService` interface. It has a constructor that takes an `IRepository<Product, Guid>` object. This repository is used to fetch the product details from the database.
> Here, we directly used `List<T>` classes, but instead, you could wrap inputs and outputs into [DTOs](../../framework/architecture/domain-driven-design/data-transfer-objects.md). In that way, it can be possible to add new properties to these DTOs without changing the signature of your integration service method (and without introducing breaking changes for your client modules).
### Exposing the Integration Service as an API
Integration services are not exposed as HTTP APIs by default. However, you can expose them as HTTP APIs if you need to. To do this, you should configure the `AbpAspNetCoreMvcOptions` in the `ConfigureServices` method of the `CloudCrmCatalogServiceModule`. Open the `CloudCrm.CatalogService` project and locate the `CloudCrmCatalogServiceModule` class. Add the following code to the `ConfigureServices` method:
```csharp
public override void ConfigureServices(ServiceConfigurationContext context)
{
// Other configurations...
Configure<AbpAspNetCoreMvcOptions>(options =>
{
options.ExposeIntegrationServices = true;
});
}
```
This code configures the `AbpAspNetCoreMvcOptions` to expose integration services as HTTP APIs. This is useful when you need to call the integration service from a different service using HTTP. You learn more about this in the [Integration Services](../../framework/api-development/integration-services.md#exposing-integration-services) document.
## Consuming the Products Integration Service
Now that we have created the `IProductIntegrationService` interface and the `ProductIntegrationService` class, we can consume this service from the Ordering service.
### Adding a Reference to the `CloudCrm.CatalogService.Contracts` Package
First, we need to add a reference to the `CloudCrm.CatalogService.Contracts` package in the Ordering service. Open the ABP Studio, and stop the application(s) if it is running. Then, open the *Solution Explorer* and right-click on the `CloudCrm.OrderingService` package. Select *Add* -> *Package Reference* command:
![add-package-reference-ordering-service](images/add-package-reference-ordering-service.png)
In the *Add Package Reference* window, select the `CloudCrm.CatalogService.Contracts` package from the *This solution* tab. Click the *OK* button to add the reference:
![add-catalog-service-contracts-reference](images/add-catalog-service-contracts-reference.png)
ABP Studio adds the package reference and arranges the [module](../../framework/architecture/modularity/basics.md) dependency.
> Instead of directly adding such a package reference, it can be best to import the module first (right-click the `CloudCrm.Ordering` module, select the _Import Module_ command and import the `CloudCrm.CatalogService` module), then install the package reference. In that way, it would be easy to see and keep track of inter-module dependencies.
### Using the Products Integration Service
Now, we can use the `IProductIntegrationService` interface to fetch the product details in the `OrderAppService` class.
Open the `OrderAppService` class (the `OrderAppService.cs` file under the `Services` folder of the `CloudCrm.OrderingService` project of the `CloudCrm.OrderingService` .NET solution) and change its content as like the following code block:
```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using CloudCrm.CatalogService.IntegrationServices;
using CloudCrm.OrderingService.Entities;
using CloudCrm.OrderingService.Enums;
using CloudCrm.OrderingService.Localization;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
namespace CloudCrm.OrderingService.Services;
public class OrderAppService : ApplicationService, IOrderAppService
{
private readonly IRepository<Order> _orderRepository;
private readonly IProductIntegrationService _productIntegrationService;
public OrderAppService(
IRepository<Order, Guid> orderRepository,
IProductIntegrationService productIntegrationService)
{
LocalizationResource = typeof(OrderingServiceResource);
_orderRepository = orderRepository;
_productIntegrationService = productIntegrationService;
}
public async Task<List<OrderDto>> GetListAsync()
{
var orders = await _orderRepository.GetListAsync();
// Prepare a list of products we need
var productIds = orders.Select(o => o.ProductId).Distinct().ToList();
var products = (await _productIntegrationService
.GetProductsByIdsAsync(productIds))
.ToDictionary(p => p.Id, p => p.Name);
var orderDtos = ObjectMapper.Map<List<Order>, List<OrderDto>>(orders);
orderDtos.ForEach(orderDto =>
{
orderDto.ProductName = products[orderDto.ProductId];
});
return orderDtos;
}
public async Task CreateAsync(OrderCreationDto input)
{
var order = new Order
{
CustomerName = input.CustomerName,
ProductId = input.ProductId,
State = OrderState.Placed
};
await _orderRepository.InsertAsync(order);
}
}
```
Now open the `OrderDto` class (the `OrderDto.cs` file under the `Services` folder of the `CloudCrm.OrderingService.Contracts` project of the `CloudCrm.OrderingService` .NET solution) and add a new property named `ProductName`:
```csharp
using System;
using CloudCrm.OrderingService.Enums;
namespace CloudCrm.OrderingService.Services;
public class OrderDto
{
public Guid Id { get; set; }
public string CustomerName { get; set; }
public Guid ProductId { get; set; }
public string ProductName { get; set; } // New property
public OrderState State { get; set; }
}
```
Lastly, open the `OrderingServiceApplicationAutoMapperProfile` class (the `OrderingServiceApplicationAutoMapperProfile.cs` file under the `ObjectMapping` folder of the `CloudCrm.Ordering` project of the `CloudCrm.Ordering` .NET solution) and ignore the `ProductName` property in the mapping configuration:
```csharp
using AutoMapper;
using CloudCrm.OrderingService.Entities;
using CloudCrm.OrderingService.Services;
using Volo.Abp.AutoMapper;
namespace CloudCrm.OrderingService.ObjectMapping;
public class OrderingServiceApplicationAutoMapperProfile : Profile
{
public OrderingServiceApplicationAutoMapperProfile()
{
CreateMap<Order, OrderDto>()
.Ignore(x => x.ProductName); // New line
}
}
```
Let's explain the changes we made:
- We added a new property named `ProductName` to the `OrderDto` class. This property will hold the product name.
- We modified the `GetListAsync` method of the `OrderAppService` class to fetch the product details using the `IProductIntegrationService` interface. We first fetch the product IDs from the orders, then call the `GetProductsByIdsAsync` method of the `IProductIntegrationService` interface to fetch the product details. Finally, we map the product names to the `OrderDto` objects.
### Generating Proxy Classes for the Integration Service
We have created the `IProductIntegrationService` interface and the `ProductIntegrationService` class in the `CloudCrm.CatalogService` solution. Now, we need to generate the proxy classes for the integration service in the `CloudCrm.OrderingService` package. First, *Build & Start* the `CloudCrm.CatalogService` application in ABP Studio *Solution Runner*. Then, open the *Solution Explorer* and right-click on the `CloudCrm.OrderingService` package. Select the *ABP CLI* -> *Generate Proxy* -> *C#* command:
![generate-proxy-catalog-service](images/generate-proxy-catalog-service.png)
It opens the *Generate C# proxies* window. Select the `CloudCrm.CatalogService` application from the *Application* dropdown list. Then, choose the *catalog* module from the *Module* dropdown list and choose the *integration* service from the *Service type* dropdown list. Check the *Without contracts* checkbox and click the *Generate* button:
![generate-catalog-service-proxy](images/generate-catalog-service-proxy.png)
### Updating the UI to Display the Product Name
Open the `Index.cshtml` file (the `Index.cshtml` file under the `Pages/Orders` folder of the `CloudCrm.Web` project of the `CloudCrm.Web` .NET solution) and update the table content to display the product name instead of the product ID:
```html
@page
@model CloudCrm.Web.Pages.Orders.Index
<h1>Orders</h1>
<abp-card>
<abp-card-body>
<abp-list-group>
@foreach (var order in Model.Orders)
{
<abp-list-group-item>
<strong>Customer:</strong> @order.CustomerName <br />
<strong>Product:</strong> @order.ProductName <br />
<strong>State:</strong> @order.State
</abp-list-group-item>
}
</abp-list-group>
</abp-card-body>
</abp-card>
```
That's it! Now, you can *Build & Start* the application and run it in ABP Studio to see the result:
Loading…
Cancel
Save