|
Before Width: | Height: | Size: 8.5 KiB |
|
After Width: | Height: | Size: 144 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 33 KiB |
|
After Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 21 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 117 KiB |
|
After Width: | Height: | Size: 29 KiB |
|
After Width: | Height: | Size: 93 KiB |
|
After Width: | Height: | Size: 26 KiB |
|
After Width: | Height: | Size: 81 KiB |
|
After Width: | Height: | Size: 178 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 56 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 72 KiB |
|
After Width: | Height: | Size: 75 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 39 KiB |
|
After Width: | Height: | Size: 62 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 63 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 66 KiB |
|
After Width: | Height: | Size: 67 KiB |
|
After Width: | Height: | Size: 77 KiB |
|
After Width: | Height: | Size: 136 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 129 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 153 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 65 KiB |
|
After Width: | Height: | Size: 24 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 30 KiB |
|
After Width: | Height: | Size: 121 KiB |
|
After Width: | Height: | Size: 58 KiB |
|
After Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 53 KiB |
|
After Width: | Height: | Size: 8.2 KiB |
|
After Width: | Height: | Size: 141 KiB |
|
After Width: | Height: | Size: 82 KiB |
|
After Width: | Height: | Size: 59 KiB |
|
After Width: | Height: | Size: 57 KiB |
|
After Width: | Height: | Size: 35 KiB |
|
After Width: | Height: | Size: 60 KiB |
|
After Width: | Height: | Size: 37 KiB |
|
After Width: | Height: | Size: 128 KiB |
@ -1,3 +1,37 @@ |
|||
# Microservice Development Tutorial |
|||
|
|||
This tutorial is work in progress. Please check later. You can check here to [see the draft tutorial](https://github.com/abpframework/abp/blob/microservice-tutorial/docs/en/tutorials/microservice/index.md). |
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Next": { |
|||
"Name": "Creating the initial solution", |
|||
"Path": "tutorials/microservice/part-01" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
> This tutorial is suitable for those who have an ABP Business or a higher [license](https://abp.io/pricing). |
|||
|
|||
ABP is designed to be a powerful platform to build microservice solutions. It provides a [microservice solution template](../../solution-templates/microservice/index.md) to easily start a sophisticated microservice solution. All of the [pre-built application modules](../../modules/index.md) are microservice compatible and the core framework fully [supports and simplifies](../../framework/architecture/microservices/index.md) distributed application development. |
|||
|
|||
In this tutorial, you will learn how to start a new microservice solution, create services and communicate between them. You will also learn to use these services from a web application through an API gateway and automatically generate CRUD pages using the [ABP Suite](../../suite/index.md) tool. |
|||
|
|||
## Tutorial Outline |
|||
|
|||
This tutorial is organized as the following parts: |
|||
|
|||
* [Part 01: Creating the initial solution](part-01.md) |
|||
* [Part 02: Creating the initial Catalog microservice](part-02.md) |
|||
* [Part 03: Building the Catalog microservice](part-03.md) |
|||
* [Part 04: Creating the initial Ordering service](part-04.md) |
|||
* [Part 05: Building the Ordering service](part-05.md) |
|||
* [Part 06: Integrating the services: HTTP API Calls](part-06.md) |
|||
* [Part 07: Integrating the services: Using Distributed Events](part-07.md) |
|||
|
|||
## Download the Source Code |
|||
|
|||
After logging in to the ABP website, you can download the source code from [here](https://abp.io/api/download/samples/cloud-crm-mvc-ef). |
|||
|
|||
## See Also |
|||
|
|||
* [Microservice solution template](../../solution-templates/microservice/index.md) |
|||
@ -0,0 +1,40 @@ |
|||
# Microservice Tutorial Part 01: Creating the Initial Solution |
|||
|
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Next": { |
|||
"Name": "Creating the initial Catalog service", |
|||
"Path": "tutorials/microservice/part-02" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Follow the *[Get Started](../../get-started/microservice.md)* guide to create a new layered web application with the following configurations: |
|||
|
|||
* **Solution name**: `CloudCrm` |
|||
* **Database Provider**: Entity Framework Core |
|||
* **Database Management System**: SQL Server |
|||
* **UI Framework**: MVC / Razor Pages |
|||
* **Mobile framework**: None |
|||
* **Public website**: Selected |
|||
|
|||
You can select the other options based on your preference. |
|||
|
|||
> **Please complete the *[Get Started](../../get-started/layered-web-application.md)* guide and run the web application before going further.** You can skip the sections after the *Running the Solution* section, if you don't prefer to complete all. |
|||
|
|||
The initial solution structure should be like the following in ABP Studio's *[Solution Explorer](../../studio/solution-explorer.md)*: |
|||
|
|||
 |
|||
|
|||
> ABP Studio will perform a few additional steps after creating your solution. **Please wait until all the background tasks are completed** before going further. |
|||
|
|||
Initially you see three folders (`apps`, `gateways` and `services`) and ~10 ABP Studio modules (depends on your preferences while creating the solution) under the `CloudCrm` ABP Studio solution. Some of these modules represent microservices, some of them represent web applications and some others represent API gateways in our system. |
|||
|
|||
> An **ABP Studio module** is typically a .NET solution and an **ABP Studio solution** is an umbrella concept for multiple .NET Solutions (see the *[Concepts](../../studio/concepts.md)* document for more). |
|||
|
|||
You can see the *[Microservice Solution Template](../../solution-templates/microservice/index.md)* document later if you want to understand the initial solution structure with all its details. However, it is not needed to follow this tutorial. |
|||
|
|||
## Summary |
|||
|
|||
In this part, you've created the initial microservice solution, which already contains a few infrastructure services. We will create our first business service in the [next part](part-02.md). |
|||
@ -0,0 +1,123 @@ |
|||
# Microservice Tutorial Part 02: Creating the initial Catalog service |
|||
|
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Previous": { |
|||
"Name": "Creating the initial solution", |
|||
"Path": "tutorials/microservice/part-01" |
|||
}, |
|||
"Next": { |
|||
"Name": "Building the Catalog service", |
|||
"Path": "tutorials/microservice/part-03" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
In this tutorial, you will create a new Catalog service and integrate it to the solution. |
|||
|
|||
## Creating the Catalog Service |
|||
|
|||
Right-click the `services` folder in the *Solution Explorer* panel, select the *Add* -> *New Module* -> *Microservice* command: |
|||
|
|||
 |
|||
|
|||
This command opens a new dialog to define the properties of the new microservice. You can use the following values to create a new microservice named `CatalogService`: |
|||
|
|||
 |
|||
|
|||
When you click the *Next* button, you are redirected to the database provider selection step. |
|||
|
|||
### Selecting the Database Type |
|||
|
|||
Here, you can select the database provider to be used by the new microservice: |
|||
|
|||
 |
|||
|
|||
Select *Entity Framework Core* option and proceed the *Next* step. |
|||
|
|||
### Integrating to the Solution |
|||
|
|||
In this step, we can select the options for integrating the new microservice to the rest of the solution components: |
|||
|
|||
 |
|||
|
|||
ABP Studio intelligently selects the right values for you, but you should still check them carefully since they directly affect what we will do in the next parts of this tutorial. |
|||
|
|||
**Ensure the options are configured the same as in the preceding figure**, and click the *Next* button. |
|||
|
|||
### Additional Options |
|||
|
|||
 |
|||
|
|||
In this step, you can select additional options for the new microservice. You can leave them as default and click the *Create* button. |
|||
|
|||
That's all, ABP Studio creates the new microservice and arranges all the integration and configuration for you. |
|||
|
|||
## Exploring the New Catalog Microservice |
|||
|
|||
In this section, we will investigate the new microservice in overall. |
|||
|
|||
### Understanding the Packages of The Service |
|||
|
|||
The new microservice is added under the `services` folder in the `CloudCrm` ABP Studio solution: |
|||
|
|||
 |
|||
|
|||
The new microservice has its own separate .NET solution that includes three packages (.NET projects): |
|||
|
|||
* `CloudCrm.CatalogService` is the main project that you will implement your service. It typically contains your [entities](../../framework/architecture/domain-driven-design/entities.md), [repositories](../../framework/architecture/domain-driven-design/repositories.md), [application services](../../framework/architecture/domain-driven-design/application-services.md), API controllers, etc. |
|||
* `CloudCrm.CatalogService.Contracts` project can be shared with the other services and applications. It typically contains interfaces of your [application services](../../framework/architecture/domain-driven-design/application-services.md), [data transfer objects](../../framework/architecture/domain-driven-design/data-transfer-objects.md), and some other types you may want to share with the clients of this microservice. |
|||
* `CloudCrm.CatalogService.Tests` is for building your unit and integration tests for this microservice. |
|||
|
|||
### Opening the Service in an IDE |
|||
|
|||
You can open the new microservice in your favorite IDE for development. As a shortcut, you can right-click it in ABP Studio, select the *Open with* -> *Visual Studio* command for example: |
|||
|
|||
 |
|||
|
|||
Here is the `CloudCrm.CatalogService` .NET solution in Visual Studio: |
|||
|
|||
 |
|||
|
|||
### Running the New Service |
|||
|
|||
You can run the solution using ABP Studio's *Solution Runner*. It will also run the new Catalog service as a part of the solution. |
|||
|
|||
> Before running the solution, **ensure that all the applications are built**. If you are not sure, right-click the root item (`CloudCrm`) in the *Solution Explorer* panel and select the *Build* -> *Graph Build* command. |
|||
|
|||
Click the *Play* button near to the solution root: |
|||
|
|||
 |
|||
|
|||
### Browsing the Catalog Service |
|||
|
|||
Once all of the applications have started, right-click the Catalog service and select the *Browse* command: |
|||
|
|||
 |
|||
|
|||
It will open the built-in browser and you will see the Swagger UI for the Catalog service: |
|||
|
|||
 |
|||
|
|||
You can test the APIs on the Swagger UI to see if the new microservice is properly working. |
|||
|
|||
### Opening the Catalog Database |
|||
|
|||
The new Catalog microservice has its own database. That database is created automatically by the microservice application, when you run the microservice. Also, [Entity Framework's database migrations](https://learn.microsoft.com/en-us/ef/core/managing-schemas/migrations/) are automatically applied by the microservice when it runs. So, you don't care about the database schema changes every time you deploy the microservice. |
|||
|
|||
Assuming you've selected SQL Server as your DBMS, you can open the SQL Server Management Studio to see its databases: |
|||
|
|||
 |
|||
|
|||
Use `localhost,1434` as the *Server name*, select the *SQL Server Authentication* as the *Authentication* type, use `sa` as the *Login* name and `myPassw@rd` as the *Password* value. You can find these values in the `appsettings.json` file in the `CloudCrm.CatalogService` project of the .NET solution of the Catalog microservice. |
|||
|
|||
Once you click the *Connect* button, you can see all the databases and explore their data: |
|||
|
|||
 |
|||
|
|||
The Catalog service's database has only three initial table. The first one is for Entity Framework Core's migration system, and the others are for ABP's [distributed event bus](../../solution-templates/microservice/distributed-events.md) to properly apply transactional events using the outbox and inbox patterns. You don't need to care about these tables since they are created and managed by Entity Framework Core and ABP. |
|||
|
|||
## Summary |
|||
|
|||
In this part of the Microservice Development Tutorial, we added a new Catalog microservice to the solution, explored its code structure and database, and browse its APIs using the Swagger UI. In the next part, we will create functionality in that new microservice. |
|||
@ -0,0 +1,126 @@ |
|||
# Microservice Tutorial Part 03: Building the Catalog service |
|||
|
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Previous": { |
|||
"Name": "Creating the initial Catalog service", |
|||
"Path": "tutorials/microservice/part-02" |
|||
}, |
|||
"Next": { |
|||
"Name": "Creating the initial Ordering service", |
|||
"Path": "tutorials/microservice/part-04" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
In the previous part, we've created a new microservice named Catalog. In this part, we will build functionality to create and manage products in our system. |
|||
|
|||
In this part, we will use [ABP Suite](../../suite/index.md) to automatically create all the necessary code for us. So, you will see how to use ABP Suite in a microservice solution. We will do everything manually while we will create the Ordering microservice in next parts, so you will learn the details better. We suggest to use ABP Suite wherever it is possible, because it saves a lot of time. You can then investigate the changes done by ABP Suite to understand what it produced. |
|||
|
|||
## Opening the ABP Suite |
|||
|
|||
First of all, **stop all the applications** in ABP Studio's *Solution Runner* panel, because ABP Suite will make changes in the solution and it will also needs to build the solution in some steps. Running the solution prevents to build it. |
|||
|
|||
Now, select the *ABP Suite* -> *Open* command on the main menu to open ABP Suite: |
|||
|
|||
 |
|||
|
|||
It will ask to you which module you want to use: |
|||
|
|||
 |
|||
|
|||
The `CloudCrm` microservice solution contains more than one .NET solution. Typically, each ABP Studio module represents a separate .NET solution (see the [concepts](../../studio/concepts.md) document). ABP Suite works on a single .NET solution to generate code, so we should select a module here. |
|||
|
|||
Select the `CloudCrm.CatalogService` module and click the *OK* button. It will open ABP Suite as shown below: |
|||
|
|||
 |
|||
|
|||
## Generating a Products Page |
|||
|
|||
In the next section, we will use ABP Suite to create a fully functional CRUD page with ABP Suite. The UI part will be in the main web application (`CloudCrm.Web`) and the application service and other parts will be generated in the Catalog microservice. |
|||
|
|||
### Configuring the Product Entity Information |
|||
|
|||
Type `Product` for the *Name* field and leave the other options as is. ABP Suite will automatically calculate proper values for you: |
|||
|
|||
 |
|||
|
|||
### Configuring Properties of the Product Entity |
|||
|
|||
Open the *Properties* tab and create the properties shown in the following figure: |
|||
|
|||
 |
|||
|
|||
Here the details: |
|||
|
|||
* `Name` is required, minimum length is `2` and maximum length is `120`. |
|||
* `Description` is not required, it is a *Text area*, not *Filterable*, not *Shown on the list page*. |
|||
* `StockCount` has a *Default value* `0`, minimum value `0` and maximum value `999999`. |
|||
* `ReleaseDate` is *Nullable*. |
|||
|
|||
You can leave the other configurations as default. |
|||
|
|||
### Generating the Code |
|||
|
|||
 |
|||
|
|||
That's all. You can click the *Save and generate* button to start the code generation process. |
|||
|
|||
 |
|||
|
|||
ABP Suite will generate the necessary code for you. It will take some time to complete the process. After the process is completed, you will see a success message, click the *OK* button. |
|||
|
|||
 |
|||
|
|||
We can now build and start the `CloudCrm.CatalogService` application by clicking the *Run* -> *Build & Start* button in the *Solution Runner* panel. |
|||
|
|||
 |
|||
|
|||
After the application is started, you can right-click and [Browse](../../studio/running-applications.md#monitoring) on the `CloudCrm.CatalogService` application to open it in the ABP Studio's pre-integrated browser. You can see the *Products* controller in the Swagger UI. |
|||
|
|||
### Generating the UI Proxy |
|||
|
|||
Now, we need to generate the [Static API Proxy](../../framework/api-development/static-csharp-clients.md) for the *Web* project. Right-click the *CloudCrm.Web* [package](../../studio/concepts.md#package) and select the *ABP CLI* -> *Generate Proxy* -> *C#* command: |
|||
|
|||
 |
|||
|
|||
It will open the *Generate C# Proxies* window. Select the `CloudCrm.CatalogService` application, and it will automatically populate the *URL* field. Select the *catalog* module, set the service type to *application*, and check the *Without contracts* checkbox, as the `CloudCrm.Web` project already depends on the `CloudCrm.CatalogService.Contracts` package: |
|||
|
|||
 |
|||
|
|||
> To be able to select the *Application*, you must *Build & Start* the related application beforehand. You can start the application using [Solution Runner](../../studio/running-applications.md) as explained in the previous parts. |
|||
|
|||
Lastly, we need to configure the use of a static HTTP client for the `CatalogService` in the `CloudCrm.Web` project. Open the `CloudCrmWebModule.cs` file in the `Web` project and add the following line to the `ConfigureServices` method: |
|||
|
|||
```csharp |
|||
//... |
|||
using CloudCrm.CatalogService; |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
// Code omitted for brevity |
|||
context.Services.AddStaticHttpClientProxies( |
|||
typeof(CloudCrmCatalogServiceContractsModule).Assembly); |
|||
} |
|||
``` |
|||
|
|||
### Running the Application |
|||
|
|||
Now, stop any application running in the *Solution Runner* panel, and then run the applications by clicking the *Run* -> *Build & Start All* button on the root item in the *Solution Runner* panel: |
|||
|
|||
 |
|||
|
|||
After the application is started, you can right-click and [Browse](../../studio/running-applications.md#monitoring) on the `CloudCrm.Web` application to open it in the ABP Studio's pre-integrated browser: |
|||
|
|||
 |
|||
|
|||
> If you can't see the *Products* menu item, you need to grant the `CatalogService` *Product* permission to the *admin* role. You can do this by navigating to *Identity Management* -> *Roles* and editing the *admin* role. Alternatively, you can restart the *CloudCrm.AdministrationService* application to automatically seed all permissions for the *admin* role. |
|||
|
|||
You can open the Sql Server Management Studio to see the created tables and data: |
|||
|
|||
 |
|||
|
|||
## Summary |
|||
|
|||
In this part, we've created a new entity named *Product* and generated the necessary code for it. We've also generated the UI proxy for the `CatalogService` application and configured the static HTTP client for it in the `Web` project. We've run the application and tested the *Products* page. |
|||
@ -0,0 +1,97 @@ |
|||
# Microservice Tutorial Part 04: Creating the initial Ordering service |
|||
|
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Previous": { |
|||
"Name": "Building the Catalog service", |
|||
"Path": "tutorials/microservice/part-03" |
|||
}, |
|||
"Next": { |
|||
"Name": "Building the Ordering service", |
|||
"Path": "tutorials/microservice/part-05" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
In the previous part, we implemented the Catalog microservice functionality using ABP Suite. In this part, we will create the Ordering microservice, and the following part will cover implementing its functionality manually. |
|||
|
|||
## Creating the Ordering Microservice |
|||
|
|||
Right-click the `services` folder in the *Solution Explorer* panel, select the *Add* -> *New Module* -> *Microservice* command: |
|||
|
|||
 |
|||
|
|||
This command opens a new dialog to define the properties of the new microservice. You can use the following values to create a new microservice named `OrderingService`: |
|||
|
|||
 |
|||
|
|||
When you click the *Next* button, you are redirected to the database provider selection step. |
|||
|
|||
### Selecting the Database Type |
|||
|
|||
Here, you can select the database provider to be used by the new microservice: |
|||
|
|||
 |
|||
|
|||
Select *Entity Framework Core* option and proceed the *Next* step. |
|||
|
|||
### Integrating to the Solution |
|||
|
|||
In this step, we can select the options for integrating the new microservice to the rest of the solution components: |
|||
|
|||
 |
|||
|
|||
ABP Studio intelligently selects the right values for you, but you should still check them carefully since they directly affect what we will do in the next parts of this tutorial. |
|||
|
|||
**Ensure the options are configured the same as in the preceding figure**, and click the *Next* button. |
|||
|
|||
### Additional Options |
|||
|
|||
 |
|||
|
|||
In this step, you can select additional options for the new microservice. You can leave them as default and click the *Create* button. |
|||
|
|||
That's all, ABP Studio creates the new microservice and arranges all the integration and configuration for you. |
|||
|
|||
## Exploring the New Ordering Microservice |
|||
|
|||
In this section, we will investigate the new microservice in overall. |
|||
|
|||
### Understanding the Solution Structure |
|||
|
|||
Just like the Catalog microservice, the Ordering microservice is a .NET solution that contains multiple projects. You can see the solution structure in the *Solution Explorer* panel: |
|||
|
|||
 |
|||
|
|||
* `CloudCrm.OrderingService` is the main project that you will implement your service. It typically contains your [entities](../../framework/architecture/domain-driven-design/entities.md), [repositories](../../framework/architecture/domain-driven-design/repositories.md), [application services](../../framework/architecture/domain-driven-design/application-services.md), API controllers, etc. |
|||
* `CloudCrm.OrderingService.Contracts` project can be shared with the other services and applications. It typically contains interfaces of your [application services](../../framework/architecture/domain-driven-design/application-services.md), [data transfer objects](../../framework/architecture/domain-driven-design/data-transfer-objects.md), and some other types you may want to share with the clients of this microservice. |
|||
* `CloudCrm.OrderingService.Tests` is for building your unit and integration tests for this microservice. |
|||
|
|||
### Running the New Service |
|||
|
|||
You can run the solution using ABP Studio's *Solution Runner*. It will also run the new Ordering service as a part of the solution. |
|||
|
|||
> Before running the solution, **ensure that all the applications are built**. If you are not sure, right-click the root item (`CloudCrm`) in the *Solution Explorer* panel and select the *Build* -> *Graph Build* command. |
|||
|
|||
Click the *Play* button near to the solution root: |
|||
|
|||
 |
|||
|
|||
### Browsing the Ordering Service |
|||
|
|||
After the application is started, you can right-click and [Browse](../../studio/running-applications.md#monitoring) on the `CloudCrm.OrderingService` application to open it in the ABP Studio's pre-integrated browser. You can see the *Orders* controller in the Swagger UI: |
|||
|
|||
 |
|||
|
|||
### Opening the Ordering Database |
|||
|
|||
You can use the SQL Server Management Studio or any other tool to connect to the Ordering service's database. Use `localhost,1434` as the *Server name*, select the *SQL Server Authentication* as the *Authentication* type, use `sa` as the *Login* name and `myPassw@rd` as the *Password* value. You can find these values in the `appsettings.json` file in the `CloudCrm.OrderingService` project of the .NET solution of the Ordering microservice: |
|||
|
|||
 |
|||
|
|||
Similarly the Ordering service's database has only three initial table. The first one is for Entity Framework Core's migration system, and the others are for ABP's [distributed event bus](../../solution-templates/microservice/distributed-events.md) to properly apply transactional events using the outbox and inbox patterns. You don't need to care about these tables since they are created and managed by Entity Framework Core and ABP. |
|||
|
|||
## Summary |
|||
|
|||
In this part, we've created the initial Ordering microservice. We will implement its functionality in the next part. |
|||
@ -0,0 +1,430 @@ |
|||
# Microservice Tutorial Part 05: Building the Ordering service |
|||
|
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Previous": { |
|||
"Name": "Creating the initial Ordering service", |
|||
"Path": "tutorials/microservice/part-04" |
|||
}, |
|||
"Next": { |
|||
"Name": "Integrating the services: HTTP API Calls", |
|||
"Path": "tutorials/microservice/part-06" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
In the previous part, we created the Ordering microservice. In this part, we will implement the functionality of the Ordering microservice manually. We will not use ABP Suite to generate the code. Instead, we will create the necessary code step by step to understand the details better. |
|||
|
|||
## Creating the Order Entity |
|||
|
|||
We will start by creating the `Order` entity, which will represent an order in our system. We'll add this entity to the `CloudCrm.OrderingService` project. Create a new folder named `Entities` and add a class named `Order` inside it: |
|||
|
|||
```csharp |
|||
using CloudCrm.OrderingService.Enums; |
|||
using Volo.Abp.Domain.Entities.Auditing; |
|||
|
|||
namespace CloudCrm.OrderingService.Entities; |
|||
|
|||
public class Order : CreationAuditedAggregateRoot<Guid> |
|||
{ |
|||
public Guid ProductId { get; set; } |
|||
public string CustomerName { get; set; } |
|||
public OrderState State { get; set; } |
|||
} |
|||
``` |
|||
|
|||
To keep this example simple, we allow users to include only a single product within an order. The `Order` entity inherits from the [CreationAuditedAggregateRoot<>](../../framework/architecture/domain-driven-design/entities.md) class. This class, provided by the ABP Framework, includes common properties like `Id`, `CreationTime`, `CreatorId`, etc. |
|||
|
|||
### Adding the OrderState Enum |
|||
|
|||
We also need to define the `OrderState` enum. In the `CloudCrm.OrderingService.Contracts` project, create a folder named `Enums` and add an `OrderState` enum inside it: |
|||
|
|||
```csharp |
|||
namespace CloudCrm.OrderingService.Enums; |
|||
|
|||
public enum OrderState : byte |
|||
{ |
|||
Placed = 0, |
|||
Delivered = 1, |
|||
Canceled = 2 |
|||
} |
|||
``` |
|||
|
|||
The final solution structure should look like this: |
|||
|
|||
 |
|||
|
|||
## Configuring the Database Mapping |
|||
|
|||
First, we need to add the `Order` entity to the `OrderingServiceDbContext` class. Open the `OrderingServiceDbContext` class in the `CloudCrm.OrderingService` project, located in the `Data` folder, and add the following code to the `DbSet` properties: |
|||
|
|||
```csharp |
|||
using CloudCrm.OrderingService.Entities; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.EntityFrameworkCore; |
|||
using Volo.Abp.EntityFrameworkCore.DistributedEvents; |
|||
using Volo.Abp.EntityFrameworkCore.Modeling; |
|||
|
|||
namespace CloudCrm.OrderingService.Data; |
|||
|
|||
[ConnectionStringName(DatabaseName)] |
|||
public class OrderingServiceDbContext : |
|||
AbpDbContext<OrderingServiceDbContext>, |
|||
IHasEventInbox, |
|||
IHasEventOutbox |
|||
{ |
|||
public const string DbTablePrefix = ""; |
|||
public const string DbSchema = null; |
|||
|
|||
public const string DatabaseName = "OrderingService"; |
|||
|
|||
public DbSet<IncomingEventRecord> IncomingEvents { get; set; } |
|||
public DbSet<OutgoingEventRecord> OutgoingEvents { get; set; } |
|||
public DbSet<Order> Orders { get; set; } // NEW: ADD DBSET PROPERTY |
|||
|
|||
// Code omitted for brevity |
|||
} |
|||
``` |
|||
|
|||
Next, we need to configure the database mapping for the `Order` entity. We'll use the [EF Core Fluent API](https://docs.microsoft.com/en-us/ef/core/modeling/relational/tables) for this configuration. In the `OrderingServiceDbContext` class add the following code to the `OnModelCreating` method: |
|||
|
|||
```csharp |
|||
protected override void OnModelCreating(ModelBuilder builder) |
|||
{ |
|||
base.OnModelCreating(builder); |
|||
|
|||
builder.ConfigureEventInbox(); |
|||
builder.ConfigureEventOutbox(); |
|||
|
|||
builder.Entity<Order>(b => |
|||
{ |
|||
// Configure table name |
|||
b.ToTable("Orders"); |
|||
|
|||
// Always call this method to set base entity properties |
|||
b.ConfigureByConvention(); |
|||
|
|||
// Properties of the entity |
|||
b.Property(q => q.CustomerName).IsRequired().HasMaxLength(120); |
|||
}); |
|||
} |
|||
``` |
|||
|
|||
In this code snippet, we configure the `Order` entity to use the `Orders` table in the database. We also specify that the `CustomerName` property is required and has a maximum length of 120 characters. |
|||
|
|||
### Add a Database Migration |
|||
|
|||
Now, we can add a new database migration. You can use Entity Framework Core's `Add-Migration` (or `dotnet ef migrations add`) terminal command, but in this tutorial, we will use ABP Studio's shortcut UI. |
|||
|
|||
Ensure that the solution has built. You can right-click the `CloudCrm.OrderingService` (under the `services` folder) on ABP Studio *Solution Explorer* and select the *Dotnet CLI* -> *Graph Build* command. |
|||
|
|||
Right-click the `CloudCrm.OrderingService` package and select the *EF Core CLI* -> *Add Migration* command: |
|||
|
|||
 |
|||
|
|||
The *Add Migration* command opens a new dialog to get a migration name: |
|||
|
|||
 |
|||
|
|||
Once you click the *OK* button, a new database migration class is added to the `Migrations` folder of the `CloudCrm.OrderingService` project: |
|||
|
|||
 |
|||
|
|||
The changes will be applied to the database during the next application startup. For more details, refer to the [database migrations on service startup](../../solution-templates/microservice/database-configurations.md#database-migrations-on-service-startup) section. |
|||
|
|||
## Creating the Application Service |
|||
|
|||
Now, we will create the application service to manage the `Order` entity. |
|||
|
|||
### Defining the Application Service Contract |
|||
|
|||
First, we need to define the application service contract under the `CloudCrm.OrderingService.Contracts` project. Return to your IDE, open the `CloudCrm.OrderingService` module's .NET solution and create an `IOrderAppService` interface under the `Services` folder for the `CloudCrm.OrderingService.Contracts` project: |
|||
|
|||
```csharp |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace CloudCrm.OrderingService.Services; |
|||
|
|||
public interface IOrderAppService : IApplicationService |
|||
{ |
|||
Task<List<OrderDto>> GetListAsync(); |
|||
Task CreateAsync(OrderCreationDto input); |
|||
} |
|||
``` |
|||
|
|||
### Defining the Data Transfer Objects |
|||
|
|||
Next, we need to define the data transfer objects (DTOs) for the `Order` entity. The `GetListAsync` and `CreateAsync` methods will use these DTOs to communicate with the client applications. |
|||
|
|||
Create a `OrderCreationDto` class under the `Services` folder in the `CloudCrm.OrderingService.Contracts` project: |
|||
|
|||
```csharp |
|||
using System; |
|||
using System.ComponentModel.DataAnnotations; |
|||
|
|||
namespace CloudCrm.OrderingService.Services; |
|||
|
|||
public class OrderCreationDto |
|||
{ |
|||
[Required] |
|||
[StringLength(150)] |
|||
public string CustomerName { get; set; } |
|||
|
|||
[Required] |
|||
public Guid ProductId { get; set; } |
|||
} |
|||
``` |
|||
|
|||
Create a `OrderDto` class under the `Services` folder in the `CloudCrm.OrderingService.Contracts` project: |
|||
|
|||
```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 OrderState State { get; set; } |
|||
} |
|||
``` |
|||
|
|||
The final solution structure should look like this: |
|||
|
|||
 |
|||
|
|||
## Implementing the Application Service |
|||
|
|||
Now, we will implement the `IOrderAppService` interface in the `OrderAppService` class. Create an `OrderAppService` class under the `Services` folder in the `CloudCrm.OrderingService` project: |
|||
|
|||
```csharp |
|||
using System; |
|||
using System.Collections.Generic; |
|||
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; |
|||
|
|||
public OrderAppService(IRepository<Order, Guid> orderRepository) |
|||
{ |
|||
LocalizationResource = typeof(OrderingServiceResource); |
|||
_orderRepository = orderRepository; |
|||
} |
|||
|
|||
public async Task<List<OrderDto>> GetListAsync() |
|||
{ |
|||
var orders = await _orderRepository.GetListAsync(); |
|||
return ObjectMapper.Map<List<Order>, List<OrderDto>>(orders); |
|||
} |
|||
|
|||
public async Task CreateAsync(OrderCreationDto input) |
|||
{ |
|||
var order = new Order |
|||
{ |
|||
CustomerName = input.CustomerName, |
|||
ProductId = input.ProductId, |
|||
State = OrderState.Placed |
|||
}; |
|||
|
|||
await _orderRepository.InsertAsync(order); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
In this code snippet, we inject the `IRepository<Order, Guid>` into the `OrderAppService` class. We use this repository to interact with the `Order` entity. The `GetListAsync` method retrieves a list of orders from the database and maps them to the `OrderDto` class. The `CreateAsync` method creates a new order entity and inserts it into the database. |
|||
|
|||
Afterward, we need to configure the *AutoMapper* object to map the `Order` entity to the `OrderDto` class. Open the `OrderingServiceApplicationAutoMapperProfile` class in the `CloudCrm.OrderingService` project, located in the `ObjectMapping` folder, and add the following code: |
|||
|
|||
```csharp |
|||
using AutoMapper; |
|||
using CloudCrm.OrderingService.Entities; |
|||
using CloudCrm.OrderingService.Services; |
|||
|
|||
namespace CloudCrm.OrderingService.ObjectMapping; |
|||
|
|||
public class OrderingServiceApplicationAutoMapperProfile : Profile |
|||
{ |
|||
public OrderingServiceApplicationAutoMapperProfile() |
|||
{ |
|||
CreateMap<Order, OrderDto>(); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
## Testing the Application Service |
|||
|
|||
Now, we can test the `OrderAppService` class using the Swagger UI. Open the Solution Runner and right-click to `CloudCrm.OrderingService` project and select the *Run* -> *Build & Start* command. After the application starts, you can open the Swagger UI by clicking to the [Browse](../../studio/running-applications.md#monitoring) command: |
|||
|
|||
 |
|||
|
|||
Expand the `api/ordering/order` API and click the *Try it out* button. Then, create a few orders by filling in the request body and clicking the *Execute* button: |
|||
|
|||
 |
|||
|
|||
If you check the database, you should see the entities created in the `Orders` table: |
|||
|
|||
 |
|||
|
|||
> Since we're using the [Auto API Controller](../../framework/api-development/auto-controllers.md) we don't need to create a controller for the `OrderAppService`. The ABP Framework automatically creates an API controller for the `OrderAppService` class. You can find the configuration in the `CloudCrmOrderingServiceModule` class, in the `ConfigureAutoControllers` method of the `CloudCrm.OrderingService` project. |
|||
|
|||
## Creating the User Interface |
|||
|
|||
Now, we will create the user interface for the Ordering module. We will use the `CloudCrm.Web` project to create the user interface. Open the `CloudCrm.Web` .NET solution in your favorite IDE. |
|||
|
|||
### Creating the Orders Page |
|||
|
|||
Create a new `Orders` folder under the `Pages` folder in the `CloudCrm.Web` project. Then, create an `Index.cshtml` Razor Page inside that new folder and edit the `Index.cshtml.cs` file as follows: |
|||
|
|||
```csharp |
|||
using CloudCrm.OrderingService.Services; |
|||
using Microsoft.AspNetCore.Mvc.RazorPages; |
|||
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; |
|||
|
|||
namespace CloudCrm.Web.Pages.Orders; |
|||
|
|||
public class Index : AbpPageModel |
|||
{ |
|||
public List<OrderDto> Orders { get; set; } |
|||
|
|||
private readonly IOrderAppService _orderAppService; |
|||
|
|||
public Index(IOrderAppService orderAppService) |
|||
{ |
|||
_orderAppService = orderAppService; |
|||
} |
|||
|
|||
public async Task OnGetAsync() |
|||
{ |
|||
Orders = await _orderAppService.GetListAsync(); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
Here, we inject the `IOrderAppService` into the `Index` Razor Page. We use this service to retrieve the list of orders from the database and assign them to the `Orders` property. Open the `Index.cshtml` file and add the following code: |
|||
|
|||
```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.ProductId <br /> |
|||
<strong>State:</strong> @order.State |
|||
</abp-list-group-item> |
|||
} |
|||
</abp-list-group> |
|||
</abp-card-body> |
|||
</abp-card> |
|||
``` |
|||
|
|||
This page shows a list of orders on the UI. We haven't created a UI to create new orders, and we will not do it to keep this tutorial simple. If you want to learn how to create advanced UIs with ABP, please follow the [Book Store tutorial](../../tutorials/book-store/index.md). |
|||
|
|||
### Generating the UI Proxy |
|||
|
|||
To select the *Application* during proxy generation, ensure that the `CloudCrm.OrderingService` is *Started* beforehand. You can start the application using [Solution Runner](../../studio/running-applications.md). |
|||
|
|||
Now, we need to generate the [Static API Proxy](../../framework/api-development/static-csharp-clients.md) for the *Web* project. Right-click the *CloudCrm.Web* [package](../../studio/concepts.md#package) and select the *ABP CLI* -> *Generate Proxy* -> *C#* command: |
|||
|
|||
 |
|||
|
|||
It will open the *Generate C# Proxies* window. Select the `CloudCrm.OrderingService` application, and it will automatically populate the *URL* field. Choose the *ordering* module and service type is *application* lastly check the *Without contracts* checkbox, since we already have a dependency on the `CloudCrm.OrderingService.Contracts` package in the `CloudCrm.Web` project: |
|||
|
|||
 |
|||
|
|||
Lastly, we need to configure the use of a static HTTP client for the `OrderingService` in the `CloudCrm.Web` project. Open the `CloudCrmWebModule.cs` file in the `Web` project and add the following line to the `ConfigureServices` method: |
|||
|
|||
```csharp |
|||
//... |
|||
using CloudCrm.OrderingService; |
|||
|
|||
public override void ConfigureServices(ServiceConfigurationContext context) |
|||
{ |
|||
// Code omitted for brevity |
|||
context.Services.AddStaticHttpClientProxies( |
|||
typeof(CloudCrmOrderingServiceContractsModule).Assembly); |
|||
} |
|||
``` |
|||
|
|||
### Adding the Menu Item |
|||
|
|||
> ABP provides a modular navigation [menu system](../../framework/ui/mvc-razor-pages/navigation-menu.md) that allows you to define the menu items in a modular way. |
|||
|
|||
Finally, we need to add a menu item to the sidebar to navigate to the `Orders` page. Open the `CloudCrmMenus` file in the `Navigation` folder of the `CloudCrm.Web` project and edit with the following code: |
|||
|
|||
```csharp |
|||
namespace CloudCrm.Web.Navigation; |
|||
|
|||
public class CloudCrmMenus |
|||
{ |
|||
private const string Prefix = "CloudCrm"; |
|||
|
|||
public const string Home = Prefix + ".Home"; |
|||
|
|||
public const string HostDashboard = Prefix + ".HostDashboard"; |
|||
|
|||
public const string TenantDashboard = Prefix + ".TenantDashboard"; |
|||
|
|||
public const string Products = Prefix + ".Products"; |
|||
|
|||
public const string Orders = Prefix + ".Orders"; // NEW: ADD MENU ITEM |
|||
} |
|||
``` |
|||
|
|||
Then, open the `CloudCrmMenuContributor` class in the `CloudCrm.Web` project, located in the `Navigation` folder, and add the following code to `ConfigureMainMenuAsync` method: |
|||
|
|||
```csharp |
|||
private static async Task ConfigureMainMenuAsync(MenuConfigurationContext context) |
|||
{ |
|||
// Code omitted for brevity |
|||
|
|||
context.Menu.AddItem( |
|||
new ApplicationMenuItem( |
|||
CloudCrmMenus.Orders, // Unique menu id |
|||
"Orders", // Menu display text |
|||
"~/Orders", // URL |
|||
"fa-solid fa-basket-shopping" // Icon CSS class |
|||
) |
|||
); |
|||
} |
|||
``` |
|||
|
|||
## Building and Running the Application |
|||
|
|||
Now, we can build and run the application to see the changes. Please stop the applications if they are running. Then open the *Solution Runner* panel, right-click the `CloudCrm` root item, and select the *Run* -> *Build & Start* command: |
|||
|
|||
 |
|||
|
|||
After the applications are started, you can *Browse* and navigate to the `Orders` page to see the list of orders: |
|||
|
|||
 |
|||
|
|||
Great! We have successfully implemented the Ordering module. However, there is a problem: |
|||
|
|||
- We see Product's GUID ID instead of its name. This is because the *Ordering* microservice has no integration with the *Catalog* microservice and doesn't have access to Product microservice's database to perform a JOIN query. |
|||
|
|||
We will solve this problem in the next part by implementing an integration service between the *Ordering* and *Catalog* microservices. |
|||
|
|||
## Summary |
|||
|
|||
In this part, we implemented the Ordering module manually. We created the `Order` entity, the `OrderState` enum, the `OrderAppService` application service, and the user interface for the `Orders` page. We also added a menu item to the sidebar to navigate to the `Orders` page. |
|||
@ -0,0 +1,311 @@ |
|||
# Microservice Tutorial Part 06: Integrating the services: HTTP API Calls |
|||
|
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Previous": { |
|||
"Name": "Building the Ordering service", |
|||
"Path": "tutorials/microservice/part-05" |
|||
}, |
|||
"Next": { |
|||
"Name": "Integrating the services: Using Distributed Events", |
|||
"Path": "tutorials/microservice/part-07" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
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 service with the Catalog service using HTTP API calls. |
|||
|
|||
## The Need for the Integration Services |
|||
|
|||
In a microservices architecture, each service is responsible for its own data and business logic. However, services often need to communicate with each other to fulfill their responsibilities. This communication can be synchronous or asynchronous, depending on the requirements. |
|||
|
|||
 |
|||
|
|||
In our case, the Ordering service needs to display the product name instead of the product ID. To achieve this, we need to call the Catalog service to retrieve the product details based on the product ID. This is a typical example of a synchronous communication pattern between microservices. As a solution to that problem, we will use an [integration service](../../framework/api-development/integration-services.md) that will handle the communication with the Catalog service. Integration service concept in ABP is designed for request/response style inter-module (in modular applications) and inter-microservice (in distributed systems) communication. |
|||
|
|||
## Creating a Products Integration Service |
|||
|
|||
First, we need to create a service that will handle the communication with the Catalog service. This service will be responsible for fetching the product details based on the product ID. |
|||
|
|||
### 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 `IntegrationServices`. Inside this folder, add a new interface named `IProductIntegrationService` with the following code: |
|||
|
|||
```csharp |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using CloudCrm.CatalogService.Products; |
|||
using Volo.Abp; |
|||
using Volo.Abp.Application.Services; |
|||
|
|||
namespace CloudCrm.CatalogService.IntegrationServices; |
|||
|
|||
[IntegrationService] |
|||
public interface IProductIntegrationService : IApplicationService |
|||
{ |
|||
Task<List<ProductDto>> GetProductsByIdsAsync(List<Guid> ids); |
|||
} |
|||
``` |
|||
|
|||
`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 applications). |
|||
|
|||
### 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 can 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: |
|||
|
|||
 |
|||
|
|||
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: |
|||
|
|||
 |
|||
|
|||
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.OrderingService`, 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.OrderingService` project of the `CloudCrm.OrderingService` .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: |
|||
|
|||
 |
|||
|
|||
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: |
|||
|
|||
 |
|||
|
|||
We have generated the proxy classes for the `IProductIntegrationService` interface. Now, we must add the *Remote Service* url to the `appsettings.json` file of the `CloudCrm.OrderingService` project. Open the `appsettings.json` file (the `appsettings.json` file of the `CloudCrm.OrderingService` project of the `CloudCrm.OrderingService` .NET solution) and add the *CatalogService* section following configuration: |
|||
|
|||
```json |
|||
{ |
|||
"RemoteServices": { |
|||
"CatalogService": { |
|||
"BaseUrl": "http://localhost:44334" |
|||
} |
|||
} |
|||
} |
|||
``` |
|||
|
|||
> **BaseUrl** refers to the base URL of the Catalog service. You can use the *Copy Url* option from the Catalog service's context menu in the ABP Studio **Solution Runner** to paste it here. |
|||
|
|||
### 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 all applications and run it in ABP Studio to see the result: |
|||
|
|||
 |
|||
|
|||
Now, the Ordering service displays the product name instead of the product ID. We have successfully integrated the Ordering service with the Catalog service using HTTP API calls. |
|||
|
|||
> **Design Tip** |
|||
> |
|||
> It is suggested that you keep that type of communication to a minimum and not couple your services with each other. It can make your solution complicated and may also decrease your system performance. When you need to do it, think about performance and try to make some optimizations. For example, if the Ordering service frequently needs product data, you can use a kind of [cache layer](../../framework/fundamentals/caching.md), so it doesn't make frequent requests to the Catalog service. |
|||
@ -0,0 +1,231 @@ |
|||
# Microservice Tutorial Part 07: Integrating the services: Using Distributed Events |
|||
|
|||
````json |
|||
//[doc-nav] |
|||
{ |
|||
"Previous": { |
|||
"Name": "Integrating the services: HTTP API Calls", |
|||
"Path": "tutorials/microservice/part-06" |
|||
} |
|||
} |
|||
```` |
|||
|
|||
Another common approach to communicating between microservices is messaging. By publishing and handling messages, a microservice can perform an operation when an event happens in another microservice. |
|||
|
|||
ABP provides two types of event buses for loosely coupled communication: |
|||
|
|||
* [Local Event Bus](../../framework/infrastructure/event-bus/local/index.md) is suitable for in-process messaging. However, it’s not suitable for microservices as it cannot communicate across different processes. For distributed systems, consider using a distributed event bus. |
|||
|
|||
* **[Distributed Event Bus](../../framework/infrastructure/event-bus/distributed/index.md)** is normal for inter-process messaging, like microservices, for publishing and subscribing to distributed events. However, ABP's distributed event bus works as local (in-process) by default (actually, it uses the Local Event Bus under the hood by default) unless you configure an external message broker. |
|||
|
|||
In this tutorial, we will use the distributed event bus to communicate between the `Order` and `Catalog` microservices. |
|||
|
|||
## Publishing an Event |
|||
|
|||
In the example scenario, we want to publish an event when a new order is placed. The Ordering service will publish the event since it knows when a new order is placed. The Catalog service will subscribe to that event and get notified when a new order is placed. This will decrease the stock count of the product related to the new order. The scenario is pretty simple; let's implement it. |
|||
|
|||
### Defining the Event Class |
|||
|
|||
Open the `CloudCrm.OrderingService` .NET solution in your IDE, create an `Events` folder and create a new class named `OrderPlacedEto` under the `CloudCrm.OrderingService.Contracts` project: |
|||
|
|||
```csharp |
|||
using System; |
|||
|
|||
namespace CloudCrm.OrderingService.Events; |
|||
|
|||
public class OrderPlacedEto |
|||
{ |
|||
public string CustomerName { get; set; } |
|||
public Guid ProductId { get; set; } |
|||
} |
|||
``` |
|||
|
|||
`OrderPlacedEto` is very simple. It is a plain C# class used to transfer data related to the event (*ETO* is an acronym for *Event Transfer Object*, a suggested naming convention but not required). You can add more properties if needed, but for this tutorial, it is more than enough. |
|||
|
|||
### Using the `IDistributedEventBus` Service |
|||
|
|||
The `IDistributedEventBus` service publishes events to the event bus. Until this point, the Ordering service only creates an Order and insert to database. Let's change that and publish `OrderPlacedEto` event, for that purpose open the `CloudCrm.OrderingService` project and update to the `OrderAppService` class as shown below: |
|||
|
|||
```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.Events; |
|||
using CloudCrm.OrderingService.Localization; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
|
|||
namespace CloudCrm.OrderingService.Services; |
|||
|
|||
public class OrderAppService : ApplicationService, IOrderAppService |
|||
{ |
|||
private readonly IRepository<Order> _orderRepository; |
|||
private readonly IProductIntegrationService _productIntegrationService; |
|||
private readonly IDistributedEventBus _distributedEventBus; |
|||
|
|||
public OrderAppService( |
|||
IRepository<Order, Guid> orderRepository, |
|||
IProductIntegrationService productIntegrationService, |
|||
IDistributedEventBus distributedEventBus) |
|||
{ |
|||
LocalizationResource = typeof(OrderingServiceResource); |
|||
|
|||
_orderRepository = orderRepository; |
|||
_productIntegrationService = productIntegrationService; |
|||
_distributedEventBus = distributedEventBus; |
|||
} |
|||
|
|||
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) |
|||
{ |
|||
// Create a new Order entity |
|||
var order = new Order |
|||
{ |
|||
CustomerName = input.CustomerName, |
|||
ProductId = input.ProductId, |
|||
State = OrderState.Placed |
|||
}; |
|||
|
|||
// Save it to the database |
|||
await _orderRepository.InsertAsync(order); |
|||
|
|||
// Publish an event so other microservices can be informed |
|||
await _distributedEventBus.PublishAsync( |
|||
new OrderPlacedEto |
|||
{ |
|||
ProductId = order.ProductId, |
|||
CustomerName = order.CustomerName |
|||
}); |
|||
} |
|||
} |
|||
``` |
|||
The `OrderAppService.CreateAsync` method creates a new `Order` entity, saves it to the database and finally publishes an `OrderPlacedEto` event. |
|||
|
|||
## Subscribing to an Event |
|||
|
|||
The Catalog service will subscribe to the `OrderPlacedEto` event and decrease the stock count of the product related to the new order. Let's implement it. |
|||
|
|||
### Adding a Reference to the `CloudCrm.OrderingService.Contracts` Package |
|||
|
|||
Since the `OrderPlacedEto` class is in the `CloudCrm.OrderingService.Contracts` project, we must add that package's reference to the Catalog service. This time, we will use the Import Module feature of ABP Studio (as an alternative to the approach we used in the Adding a Reference to the `CloudCrm.CatalogService.Contracts` Package section of the [previous part](./part-06.md#adding-a-reference-to-the-cloudcrmcatalogservicecontracts-package)). |
|||
|
|||
Open the ABP Studio UI and stop the applications if they are running. Then, open the *Solution Explorer* panel and right-click on the `CloudCrm.CatalogService`. Select *Import Module* from the context menu: |
|||
|
|||
 |
|||
|
|||
In the opening dialog, find and select the `CloudCrm.OrderingService` module, check the *Install this module* option, click the *OK* button: |
|||
|
|||
 |
|||
|
|||
Once you click the OK button, the Ordering service is imported to the Catalog service. It opens the *Install Module* dialog: |
|||
|
|||
 |
|||
|
|||
Here, select the `CloudCrm.OrderingService.Contracts` package on the left side (because we want to add that package reference) and `CloudCrm.CatalogService` package on the middle area (because we want to add the package reference to that project). |
|||
|
|||
You can check the ABP Studio's *Solution Explorer* panel to see the module and the project reference (dependency): |
|||
|
|||
 |
|||
|
|||
### Handling the `OrderPlacedEto` Event |
|||
|
|||
Now, it's time to handle the `OrderPlacedEto` event in the Catalog service. Open the `CloudCrm.CatalogService` .NET solution in your IDE. Create a new `Orders` folder, and add a new class named `OrderEventHandler` inside that folder within the `CloudCrm.CatalogService` project: |
|||
|
|||
```csharp |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using CloudCrm.CatalogService.Products; |
|||
using CloudCrm.OrderingService.Events; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.EventBus.Distributed; |
|||
|
|||
namespace CloudCrm.CatalogService.Orders; |
|||
|
|||
public class OrderEventHandler : |
|||
IDistributedEventHandler<OrderPlacedEto>, |
|||
ITransientDependency |
|||
{ |
|||
private readonly IRepository<Product, Guid> _productRepository; |
|||
|
|||
public OrderEventHandler(IRepository<Product, Guid> productRepository) |
|||
{ |
|||
_productRepository = productRepository; |
|||
} |
|||
|
|||
public async Task HandleEventAsync(OrderPlacedEto eventData) |
|||
{ |
|||
// Find the related product |
|||
var product = await _productRepository.FindAsync(eventData.ProductId); |
|||
if (product == null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
// Decrease the stock count |
|||
product.StockCount = product.StockCount - 1; |
|||
|
|||
// Update the entity in the database |
|||
await _productRepository.UpdateAsync(product); |
|||
} |
|||
} |
|||
``` |
|||
|
|||
The `OrderEventHandler` class implements the `IDistributedEventHandler<OrderPlacedEto>` interface to handle the `OrderPlacedEto` event. When the event is published, the `HandleEventAsync` method is called. In this method, we find the related product, decrease the stock count by one, and update the entity in the database. |
|||
|
|||
Implementing `ITransientDependency` registers the `OrderEventHandler` class to the dependency injection system as a transient object. |
|||
|
|||
### Testing the Order Creation |
|||
|
|||
To keep this tutorial simple, we will not implement a user interface for creating orders. Instead, we will use the Swagger UI to create an order. Open the *Solution Runner* panel in ABP Studio and use *Build & Start* to launch the `CloudCrm.OrderingService` and `CloudCrm.CatalogService` applications. Then, go to *Run* -> *Start All* to start the remaining applications listed in the [Solution Runner root item](../../studio/running-applications.md#run). |
|||
|
|||
Once the application is running and ready, [Browse](../../studio/running-applications.md#c-application) the `CloudCrm.OrderingService` application. Use the `POST /api/ordering/order` endpoint to create a new order: |
|||
|
|||
 |
|||
|
|||
Find the *Order* API, click the *Try it out* button, enter a sample value the *Request body* section, and click the *Execute* button: |
|||
|
|||
```json |
|||
{ |
|||
"customerName": "David", |
|||
"productId": "5995897b-1de9-7272-b31c-3a165bbe7b18" |
|||
} |
|||
``` |
|||
|
|||
> **IMPORTANT:** Here, you should type a valid Product Id from the Products table of your database! |
|||
|
|||
Once you press the *Execute* button, a new order is created. At that point, you can check the `/Orders` page to see if the new order is listed. You can also check the `/Products` page to see if the stock count of the related product is decreased by one in the `CloudCrm.Web` application. |
|||
|
|||
Here are sample screenshots from the Orders and Products pages of the `CloudCrm.Web` application: |
|||
|
|||
 |
|||
|
|||
We placed a new order for *Product A*. As a result, the stock count of *Product A* is decreased from 53 to 52 and a new line is added to the Orders page. |
|||
|
|||
## Conclusion |
|||
|
|||
In this tutorial, we used the distributed event bus to communicate between the `Order` and `Catalog` microservices. We published an event when a new order is placed and handled that event in the Catalog service to decrease the stock count of the related product. This is a simple example, but it shows how you can use distributed events to communicate between microservices. |
|||