# Application Services Best Practices & Conventions
> This document offers best practices for implementing Application Services classes in your modules and applications based on Domain-Driven-Design principles.
>
> **Ensure you've read the [*Application Services*](../domain-driven-design/application-services.md) document first.**
## General
* **Do** create an application service for each **aggregate root**.
### Application Service Interface
## Application Service Interface
* **Do** define an `interface` for each application service in the **application contracts** package.
* **Do** inherit from the `IApplicationService` interface.
@ -11,11 +17,11 @@
* **Do not** get/return entities for the service methods.
* **Do** define DTOs based on the [DTO best practices](data-transfer-objects.md).
#### Outputs
### Outputs
* **Avoid** to define too many output DTOs for same or related entities. Instead, define a **basic** and a **detailed** DTO for an entity.
##### Basic DTO
#### Basic DTO
**Do** define a **basic** DTO for an aggregate root.
@ -44,7 +50,7 @@ public class IssueLabelDto
}
```
##### Detailed DTO
#### Detailed DTO
**Do** define a **detailed** DTO for an entity if it has reference(s) to other aggregate roots.
@ -81,20 +87,20 @@ public class LabelDto : ExtensibleEntityDto<Guid>
}
````
#### Inputs
### Inputs
* **Do not** define any property in an input DTO that is not used in the service class.
* **Do not** share input DTOs between application service methods.
* **Do not** inherit an input DTO class from another one.
* **May** inherit from an abstract base DTO class and share some properties between different DTOs in that way. However, should be very careful in that case because manipulating the base DTO would effect all related DTOs and service methods. Avoid from that as a good practice.
#### Methods
### Methods
* **Do** define service methods as asynchronous with **Async** postfix.
* **Do not** repeat the entity name in the method names.
* Example: Define `GetAsync(...)` instead of `GetProductAsync(...)` in the `IProductAppService`.
##### Getting A Single Entity
#### Getting A Single Entity
* **Do** use the `GetAsync`**method name**.
* **Do** get Id with a **primitive** method parameter.
@ -104,7 +110,7 @@ public class LabelDto : ExtensibleEntityDto<Guid>
Task<QuestionWithDetailsDto> GetAsync(Guid id);
````
##### Getting A List Of Entities
#### Getting A List Of Entities
* **Do** use the `GetListAsync`**method name**.
* **Do** get a single DTO argument for **filtering**, **sorting** and **paging** if necessary.
This method votes a question and returns the current score of the question.
### Application Service Implementation
## Application Service Implementation
* **Do** develop the application layer **completely independent from the web layer**.
* **Do** implement application service interfaces in the **application layer**.
@ -195,30 +201,30 @@ This method votes a question and returns the current score of the question.
* **Do** make all public methods **virtual**, so developers may inherit and override them.
* **Do not** make **private** methods. Instead make them **protected virtual**, so developers may inherit and override them.
#### Using Repositories
### Using Repositories
* **Do** use the specifically designed repositories (like `IProductRepository`).
* **Do not** use generic repositories (like `IRepository<Product>`).
#### Querying Data
### Querying Data
* **Do not** use LINQ/SQL for querying data from database inside the application service methods. It's repository's responsibility to perform LINQ/SQL queries from the data source.
#### Extra Properties
### Extra Properties
* **Do** use either `MapExtraPropertiesTo` extension method ([see](../../fundamentals/object-extensions.md)) or configure the object mapper (`MapExtraProperties`) to allow application developers to be able to extend the objects and services.
#### Manipulating / Deleting Entities
### Manipulating / Deleting Entities
* **Do** always get all the related entities from repositories to perform the operations on them.
* **Do** call repository's Update/UpdateAsync method after updating an entity. Because, not all database APIs support change tracking & auto update.
#### Handle files
### Handle files
* **Do not** use any web components like `IFormFile` or `Stream` in the application services. If you want to serve a file you can use `byte[]`.
* **Do** use a `Controller` to handle file uploading then pass the `byte[]` of the file to the application service method.
#### Using Other Application Services
### Using Other Application Services
* **Do not** use other application services of the same module/application. Instead;
# Data Transfer Objects Best Practices & Conventions
> This document offers best practices for implementing Data Transfer Object classes in your modules and applications based on Domain-Driven-Design principles.
>
> **Ensure you've read the [*Data Transfer Objects*](../domain-driven-design/data-transfer-objects.md) document first.**
## General
* **Do** define DTOs in the **application contracts** package.
* **Do** inherit from the pre-built **base DTO classes** where possible and necessary (like `EntityDto<TKey>`, `CreationAuditedEntityDto<TKey>`, `AuditedEntityDto<TKey>`, `FullAuditedEntityDto<TKey>` and so on).
* **Do** inherit from the **extensible DTO** classes for the **aggregate roots** (like `ExtensibleAuditedEntityDto<TKey>`), because aggregate roots are extensible objects and extra properties are mapped to DTOs in this way.
> This document offers best practices for implementing Domain Service classes in your modules and applications based on Domain-Driven-Design principles.
>
> **Ensure you've read the [*Domain Services*](../domain-driven-design/domain-services.md) document first.**
## Domain Services
- **Do** define domain services in the **domain layer**.
- **Do not** create interfaces for the domain services **unless** you have a good reason to (like mock and test different implementations).
@ -14,7 +18,7 @@ public class IssueManager : DomainService
}
```
### Domain Service Methods
## Domain Service Methods
- **Do not** define `GET` methods. `GET` methods do not change the state of an entity. Hence, use the repository directly in the Application Service instead of Domain Service method.
- **Do not** return `DTO`. Return only domain objects when you need.
- **Do not** involve authenticated user logic. Instead, define extra parameter and send the related data of ` CurrentUser` from the Application Service layer.
> This document offers best practices for implementing Aggregate Root and Entity classes in your modules and applications based on Domain-Driven-Design principles.
>
> **Ensure you've read the [*Entities*](../domain-driven-design/entities.md) document first.**
## Entities
Every aggregate root is also an entity. So, these rules are valid for aggregate roots too unless aggregate root rules override them.
- **Do** define entities in the **domain layer**.
#### Primary Constructor
### Primary Constructor
* **Do** define a **primary constructor** that ensures the validity of the entity on creation. Primary constructors are used to create a new instance of the entity by the application code.
@ -14,15 +18,15 @@ Every aggregate root is also an entity. So, these rules are valid for aggregate
- **Do** always initialize sub collections in the primary constructor.
- **Do not** generate `Guid` keys inside the constructor. Get it as a parameter, so the calling code will use `IGuidGenerator` to generate a new `Guid` value.
#### Parameterless Constructor
### Parameterless Constructor
- **Do** always define a `protected` parameterless constructor to be compatible with ORMs.
#### References
### References
- **Do** always **reference** to other aggregate roots **by Id**. Never add navigation properties to other aggregate roots.
#### Other Class Members
### Other Class Members
- **Do** always define properties and methods as `virtual` (except `private` methods, obviously). Because some ORMs and dynamic proxy tools require it.
- **Do** keep the entity as always **valid** and **consistent** within its own boundary.
@ -30,27 +34,27 @@ Every aggregate root is also an entity. So, these rules are valid for aggregate
- **Do** define `public `, `internal` or `protected internal` (virtual) **methods** to change the properties (with non-public setters) if necessary.
- **Do** return the entity object (`this`) from the setter methods.
### Aggregate Roots
## Aggregate Roots
#### Primary Keys
### Primary Keys
* **Do** always use a **Id** property for the aggregate root key.
* **Do not** use **composite keys** for aggregate roots.
* **Do** use **Guid** as the **primary key** of all aggregate roots.
#### Base Class
### Base Class
* **Do** inherit from the `AggregateRoot<TKey>` or one of the audited classes (`CreationAuditedAggregateRoot<TKey>`, `AuditedAggregateRoot<TKey>` or `FullAuditedAggregateRoot<TKey>`) based on requirements.
#### Aggregate Boundary
### Aggregate Boundary
* **Do** keep aggregates **as small as possible**. Most of the aggregates will only have primitive properties and will not have sub collections. Consider these as design decisions:
* **Performance** &**memory** cost of loading & saving aggregates (keep in mind that an aggregate is normally loaded & saved as a single unit). Larger aggregates will consume more CPU & memory.
* **Consistency** &**validity** boundary.
### Example
## Example
#### Aggregate Root
### Aggregate Root
````C#
public class Issue : FullAuditedAggregateRoot<Guid> //Using Guid as the key/identifier
@ -130,7 +134,7 @@ public class Issue : FullAuditedAggregateRoot<Guid> //Using Guid as the key/iden
}
````
#### The Entity
### Entity
````C#
public class IssueLabel : Entity
@ -151,11 +155,12 @@ public class IssueLabel : Entity
- **Do** always use a short `TablePrefix` value for a module to create **unique table names** in a shared database. `Abp` table prefix is reserved for ABP core modules.
- **Do** set `Schema` to `null` as default.
### Model Mapping
## Model Mapping
- **Do** explicitly **configure all entities** by overriding the `OnModelCreating` method of the `DbContext`. Example:
@ -100,7 +104,7 @@ public static class IdentityDbContextModelBuilderExtensions
* **Do** call `b.ConfigureByConvention();` for each entity mapping (as shown above).
### Repository Implementation
## Repository Implementation
- **Do****inherit** the repository from the `EfCoreRepository<TDbContext, TEntity, TKey>` class and implement the corresponding repository interface. Example:
@ -168,7 +172,7 @@ public override async Task<IQueryable<IdentityUser>> WithDetailsAsync()
}
````
### Module Class
## Module Class
- **Do** define a module class for the Entity Framework Core integration package.
- **Do** add `DbContext` to the `IServiceCollection` using the `AddAbpDbContext<TDbContext>` method.
> This document offers best practices for implementing MongoDB integration in your modules and applications.
>
> **Ensure you've read the [*MongoDB Integration*](../../data/entity-framework-core/index.md) document first.**
## General
* Do define a separated `MongoDbContext` interface and class for each module.
### MongoDbContext Interface
## MongoDbContext Interface
- **Do** define an **interface** for the `MongoDbContext` that inherits from `IAbpMongoDbContext`.
- **Do** add a `ConnectionStringName`**attribute** to the `MongoDbContext` interface.
@ -17,7 +23,7 @@ public interface IAbpIdentityMongoDbContext : IAbpMongoDbContext
}
````
### MongoDbContext class
## MongoDbContext class
- **Do** inherit the `MongoDbContext` from the `AbpMongoDbContext` class.
- **Do** add a `ConnectionStringName` attribute to the `MongoDbContext` class.
@ -34,7 +40,7 @@ public class AbpIdentityMongoDbContext : AbpMongoDbContext, IAbpIdentityMongoDbC
}
```
### Collection Prefix
## Collection Prefix
- **Do** add static `CollectionPrefix`**property** to the `DbContext` class. Set default value from a constant. Example:
@ -46,7 +52,7 @@ Used the same constant defined for the EF Core integration table prefix in this
- **Do** always use a short `CollectionPrefix` value for a module to create **unique collection names** in a shared database. `Abp` collection prefix is reserved for ABP core modules.
### Collection Mapping
## Collection Mapping
- **Do** explicitly **configure all aggregate roots** by overriding the `CreateModel` method of the `MongoDbContext`. Example:
@ -83,7 +89,7 @@ public static class AbpIdentityMongoDbContextExtensions
}
```
### Repository Implementation
## Repository Implementation
- **Do****inherit** the repository from the `MongoDbRepository<TMongoDbContext, TEntity, TKey>` class and implement the corresponding repository interface. Example:
@ -124,7 +130,7 @@ public async Task<IdentityUser> FindByNormalizedUserNameAsync(
* Using `IQueryable<TEntity>` makes the code as much as similar to the EF Core repository implementation and easy to write and read.
* **Do** implement data filtering if it is not possible to use the `GetMongoQueryable()` method.
### Module Class
## Module Class
- **Do** define a module class for the MongoDB integration package.
- **Do** add `MongoDbContext` to the `IServiceCollection` using the `AddMongoDbContext<TMongoDbContext>` method.
> This document offers best practices for implementing Repository classes in your modules and applications based on Domain-Driven-Design principles.
>
> **Ensure you've read the [*Repositories*](../domain-driven-design/repositories.md) document first.**
## Repository Interfaces
* **Do** define repository interfaces in the **domain layer**.
* **Do** define a repository interface (like `IIdentityUserRepository`) and create its corresponding implementations for **each aggregate root**.
@ -30,7 +34,7 @@ public interface IIdentityUserRepository : IBasicRepository<IdentityUser, Guid>
* **Do** inherit the repository interface from `IBasicRepository<TEntity, TKey>` (as normally) or a lower-featured interface, like `IReadOnlyRepository<TEntity, TKey>` (if it's needed).
* **Do not** define repositories for entities those are **not aggregate roots**.
### Repository Methods
## Repository Methods
* **Do** define all repository methods as **asynchronous**.
* **Do** add an **optional**`cancellationToken` parameter to every method of the repository. Example:
* **Avoid** to create projection classes for entities to get less property of an entity from the repository. Example: Avoid to create BasicUserView class to select a few properties needed for the use case needs. Instead, directly use the aggregate root class. However, there may be some exceptions for this rule, where:
* Performance is so critical for the use case and getting the whole aggregate root highly impacts the performance.
**Data Transfer Objects** (DTO) are used to transfer data between the **Application Layer** and the **Presentation Layer** or other type of clients.
Typically, an [application service](./application-services.md) is called from the presentation layer (optionally) with a **DTO** as the parameter. It uses domain objects to **perform some specific business logic** and (optionally) returns a DTO back to the presentation layer. Thus, the presentation layer is completely **isolated** from domain layer.
In a [Domain Driven Design](../domain-driven-design) (DDD) solution, the core business logic is generally implemented in aggregates ([entities](./entities.md)) and the Domain Services. Creating a Domain Service is especially needed when;
* You implement a core domain logic that depends on some services (like repositories or other external services).
Follow the *[Get Started](../../get-started/layered-web-application.md)* guide to create a new layered web application with the following configuration:
Follow the *[Get Started](../../get-started/single-layer-web-application.md)* guide to create a single layer web application with the following configuration:
* **Solution name**: `ModularCrm`
* **UI Framework**: ASP.NET Core MVC / Razor Pages
@ -18,7 +22,7 @@ Follow the *[Get Started](../../get-started/layered-web-application.md)* guide t
You can select the other options based on your preference.
> **Please complete the [Get Stared](../../get-started/layered-web-application.md) guide and run the web application before going further.**
> **Please complete the [Get Started](../../get-started/single-layer-web-application.md) guide and run the web application before going further.**
The initial solution structure should be like the following in ABP Studio's *[Solution Explorer](../../studio/solution-explorer.md)*:
@ -28,10 +32,10 @@ Initially, you see a `ModularCrm` solution and a `ModularCrm` module under that
> 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).
`ModularCrm` module is your main application, which is a layered .NET solution that consists of several packages (.NET projects). You can expand the `ModularCrm` module to see its packages:
The `ModularCrm` module is the core of your application, built as a single-layer ASP.NET Core Web application. You can expand the `ModularCrm` module to see:
We've created the initial layered monolith solution. In the next part, we will learn how to create a new application module and install it to the main application.
We've created the initial single layer monolith solution. In the next part, we will learn how to create a new application module and install it to the main application.
This dialog simplifies installing a multi-layer module to a multi-layer application. It automatically determines which package of the `ModularCrm.Products` module should be installed to which package of the main application. For example, the `ModularCrm.Products.Domain` package is installed to the `ModularCrm.Domain` package. In that way, you can use domain objects ([entities](../../framework/architecture/domain-driven-design/entities.md), [repositories](../../framework/architecture/domain-driven-design/repositories.md), ...) of the products module from the domain layer of your main application.
This dialog simplifies installing a multi-layer module to a single-layer application. It automatically determines which package of the `ModularCrm.Products` module should be installed to which package of the main application.
The default package match is good for this tutorial, so you can click the *OK* button to proceed.
@ -131,7 +132,7 @@ Graph Build is a dotnet CLI command that recursively builds all the referenced d
### Run the Main Application
Open the *Solution Runner* panel, click the *Play* button (near to the solution root), right-click the `ModularCrm.Web` application and select the *Browse* command. It will open the web application in the built-in browser. Then you can navigate to the *Products* page on the main menu of the application to see the Products page that is coming from the `ModularCrm.Products` module:
Open the *Solution Runner* panel, click the *Play* button (near to the solution root), right-click the `ModularCrm` application and select the *Browse* command. It will open the web application in the built-in browser. Then you can navigate to the *Products* page on the main menu of the application to see the Products page that is coming from the `ModularCrm.Products` module:
@ -214,7 +213,7 @@ Now, we can add a new database migration. You can use Entity Framework Core's `A
Ensure that the solution has built. You can right-click the `ModularCrm` (under the `main` folder) on ABP Studio *Solution Runner* and select the *Dotnet CLI* -> *Graph Build* command.
Right-click the `ModularCrm.EntityFrameworkCore` package and select the *EF Core CLI* -> *Add Migration* command:
Right-click the `ModularCrm` package and select the *EF Core CLI* -> *Add Migration* command:
@ -369,7 +368,7 @@ For this application, we don't need to create HTTP API endpoints for the product
* You can create a regular ASP.NET Core Controller class in the `ModularCrm.Products.HttpApi` project, inject `IProductAppService` and use it to create wrapper methods. We will do this later while we create the Ordering module.
* Alternatively, you can use the ABP's [Auto API Controllers](../../framework/api-development/auto-controllers.md) feature to expose your application services as API controllers by conventions. We will do it here.
Open the `ModularCrmWebModule` class in the main application's solution (the `ModularCrm` solution), find the `PreConfigureServices` method and add the following lines inside that method:
Open the `ModularCrmModule` class in the main application's solution (the `ModularCrm` solution), find the `PreConfigureServices` method and add the following lines inside that method:
````csharp
PreConfigure<IMvcBuilder>(mvcBuilder =>
@ -385,8 +384,8 @@ Then open the `ConfigureAutoApiControllers` method of the same class and add a s
@ -404,7 +403,7 @@ This section will create a few example products using the [Swagger UI](../../fra
Now, right-click the `ModularCrm` under the `main` folder in the Solution Explorer panel and select the *Dotnet CLI* -> *Graph Build* command. This will ensure that the product module and the main application are built and ready to run.
After the build process completes, open the Solution Runner panel and click the *Play* button near the solution root. Once the `ModularCrm.Web` application runs, we can right-click it and select the *Browse* command to open the user interface.
After the build process completes, open the Solution Runner panel and click the *Play* button near the solution root. Once the `ModularCrm` application runs, we can right-click it and select the *Browse* command to open the user interface.
Once you see the user interface of the web application, type `/swagger` at the end of the URL to open the Swagger UI. If you scroll down, you should see the `Products` API:
@ -486,7 +485,7 @@ Here, we simply use the `IProductAppService` to get a list of all products and a
</abp-card>
````
You can build the product module's .NET solution (`ModularCrm.Products`), then right-click the `ModularCrm.Web` application on ABP Studio's solution runner and select the *Build & Restart* command:
Right-click the `ModularCrm` application on ABP Studio's solution runner and select the *Start* command:
Select the `ModuleCrm.Ordering` package from the left area and the `ModularCrm.Domain` package from the middle area. Then, select the `ModularCrm.Ordering.UI` package from the left area and the `ModularCrm.Web` package from the middle area, as shown in the preceding figure. Finally, click *OK*.
> Since the Ordering module is not layered, we didn't install its packages to the layers of our main application. We are installing it only to `ModularCrm.Domain`. In this way, we can use the Ordering module from any layer of our application since `ModularCrm.Domain` is one of the core packages of our application. If you build your modules as non-layered and you don't have much code in the main application's .NET solution, you can also consider creating a non-layered main application that composes these modules.
Select the `ModuleCrm.Ordering` and `ModularCrm.Ordering.UI` packages from the left area and the `ModularCrm` package from the middle area as shown in the preceding figure. Finally, click *OK*.
In this part of the tutorial, we've created a standard module. This allows you to create modules or applications with a different structure. In the next part, we will add functionality to the Ordering module.
@ -28,7 +28,7 @@ Create an `Order` class to the `ModularCrm.Ordering` project (open an `Entities`
````csharp
using System;
using ModularCrm.Ordering.Contracts.Enums;
using ModularCrm.Ordering.Enums;
using Volo.Abp.Domain.Entities.Auditing;
namespace ModularCrm.Ordering.Entities
@ -156,7 +156,7 @@ public static class OrderingDbContextModelCreatingExtensions
#### Configuring the Main Application
Open the main application's solution in your IDE, find the `ModularCrmDbContext` class under the `ModularCrm.EntityFrameworkCore` project and follow the 3 steps below:
Open the main application's solution in your IDE, find the `ModularCrmDbContext` class under the `ModularCrm` project's `Data` folder, and follow the 3 steps below:
**(1)** Add the following attribute on top of the `ModularCrmDbContext` class:
@ -171,8 +171,6 @@ The `ReplaceDbContext` attribute allows the use of the `ModularCrmDbContext` cla
````csharp
public class ModularCrmDbContext :
AbpDbContext<ModularCrmDbContext>,
ITenantManagementDbContext,
IIdentityDbContext,
IProductsDbContext,
IOrderingDbContext //NEW: IMPLEMENT THE INTERFACE
{
@ -200,7 +198,7 @@ Now, we can add a new database migration. You can use Entity Framework Core's `A
Ensure that the solution has built. You can right-click the `ModularCrm` (under the `main` folder) on ABP Studio *Solution Runner* and select the *Dotnet CLI* -> *Graph Build* command.
Right-click the `ModularCrm.EntityFrameworkCore` package and select the *EF Core CLI* -> *Add Migration* command:
Right-click the `ModularCrm` package and select the *EF Core CLI* -> *Add Migration* command:
@ -347,14 +345,14 @@ public class OrderAppService : OrderingAppService, IOrderAppService
}
````
Open the `ModularCrmWebModule` class in the main application's solution (the `ModularCrm` solution), find the `ConfigureAutoApiControllers` method and add the following lines inside that method:
Open the `ModularCrmModule` class in the main application's solution (the `ModularCrm` solution), find the `ConfigureAutoApiControllers` method and add the following lines inside that method:
@ -369,7 +367,7 @@ This section will create a few example orders using the [Swagger UI](../../frame
Now, right-click the `ModularCrm` under the `main` folder in the Solution Explorer panel and select the *Dotnet CLI* -> *Graph Build* command. This will ensure that the order module and the main application are built and ready to run.
After the build process completes, open the Solution Runner panel and click the *Play* button near the solution root. Once the `ModularCrm.Web` application runs, we can right-click it and select the *Browse* command to open the user interface.
After the build process completes, open the Solution Runner panel and click the *Play* button near the solution root. Once the `ModularCrm` application runs, we can right-click it and select the *Browse* command to open the user interface.
Once you see the user interface of the web application, type `/swagger` at the end of the URL to open the Swagger UI. If you scroll down, you should see the `Orders` API:
@ -487,11 +485,11 @@ public class OrderingMenuContributor : IMenuContributor
### Building the Application
Now, we will run the application to see the result. Please stop the application if it is already running. Then open the *Solution Runner* panel, right-click the `ModularCrm.Web` application, and select the *Build* -> *Graph Build* command:
Now, we will run the application to see the result. Please stop the application if it is already running. Then open the *Solution Runner* panel, right-click the `ModularCrm` application, and select the *Build* -> *Graph Build* command:
We've performed a graph build since we've made a change on a module, and more than building the main application is needed. *Graph Build* command also builds the depended modules if necessary. Alternatively, you could build the Ordering module first (on ABP Studio or your IDE). This approach can be faster if you have too many modules and you make a change in one of the modules. Now you can run the application by right-clicking the `ModularCrm.Web` application and selecting the *Start* command.
We've performed a graph build since we've made a change on a module, and more than building the main application is needed. *Graph Build* command also builds the depended modules if necessary. Alternatively, you could build the Ordering module first (on ABP Studio or your IDE). This approach can be faster if you have too many modules and you make a change in one of the modules. Now you can run the application by right-clicking the `ModularCrm` application and selecting the *Start* command.
@ -19,7 +19,7 @@ Another common approach to communicating between modules is messaging. By publis
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. Since in a modular monolith, both of publisher and subscriber are in the same process, they can communicate in-process, without needing an external message broker.
* **[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.
* [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.
If you consider converting your modular monolith to a microservice system later, it is best to use the Distributed Event Bus with default local/in-process implementation. It already supports database-level transactional event execution and has no performance penalty. If you switch to an external provider ([RabbitMQ](../../framework/infrastructure/event-bus/distributed/rabbitmq.md), [Kafka](../../framework/infrastructure/event-bus/distributed/kafka.md), etc.), you don't need to change your application code.
@ -218,7 +218,7 @@ We inject the product repository and update the stock count in the event handler
To keep this tutorial more focused, we will not create a UI for creating an order. You can easily create a form to create an order on your user interface. In this section, we will test it just using the Swagger UI.
Graph build the `ModularCrm.Web` application, run it on the ABP Studio's *Solution Runner* panel and browse the application UI as demonstrated earlier.
Graph build the `ModularCrm` application, run it on the ABP Studio's *Solution Runner* panel and browse the application UI as demonstrated earlier.
Once the application is running and ready, manually type `/swagger` to the end of the URL and press the ENTER key. You should see the Swagger UI that is used to discover and test your HTTP APIs:
@ -228,8 +228,8 @@ Find the *Orders* API, click the *Try it out* button, enter a sample value the t
@ -43,7 +43,7 @@ We will define the `IOrderReportingAppService` interface in the `ModularCrm.Appl
As the first step, we should reference the `ModularCrm.Ordering.Contracts` package (of the `ModularCrm.Ordering` module) since we will reuse the `OrderState` enum defined in that package.
Open the ABP Studio's *Solution Explorer* panel, right-click the `ModularCrm.Application.Contracts` package and select the *Add Package Reference* command:
Open the ABP Studio's *Solution Explorer* panel, right-click the `ModularCrm` package and select the *Add Package Reference* command:
@ -55,7 +55,7 @@ The package reference has been added, and we can now use the types in the `Modul
#### Defining the `IOrderReportingAppService` Interface
Open the main `ModularCrm` .NET solution in your IDE, find the `ModularCrm.Application.Contracts` project, create an `Orders` folder and add an `IOrderReportingAppService` interface. Here is the definition of that interface:
Open the main `ModularCrm` .NET solution in your IDE, create an `Orders` folder under the `Services` folder and add an `IOrderReportingAppService` interface. Here is the definition of that interface:
````csharp
using System.Collections.Generic;
@ -71,7 +71,7 @@ namespace ModularCrm.Orders
}
````
We have a single method, `GetLatestOrders`, that will return a list of the latest orders. We should also define the `OrderReportDto` class that that method returns. Create the following class in the same `Orders` folder:
We have a single method, `GetLatestOrders`, that will return a list of the latest orders. We should also define the `OrderReportDto` class that that method returns. Create the `Orders` folder under the `Services/Dtos` folder and create a class named `OrderReportDto`.
````csharp
using System;
@ -93,7 +93,7 @@ namespace ModularCrm.Orders
}
````
`OrderReportDto` contains data from both the `Order` and `Product` entities. We could use the `OrderState` since we have a reference to the package that defines that enum.
`OrderReportDto` contains data from both the `Order` and `Product` entities. We could use the `OrderState` since we have a reference to the package that defines that enum.
After adding these files, the final folder structure should be like this:
@ -101,9 +101,7 @@ After adding these files, the final folder structure should be like this:
### Implementing the `OrderReportingAppService` Class
Create an `Orders` folder inside the `ModularCrm.Application` project and add a class named `OrderReportingAppService` inside it. The final folder structure should be like this:
Create a class named `OrderReportingAppService` under the `Services/Orders` folder.
Open the `OrderReportingAppService.cs` file and change its content by the following code block:
@ -176,7 +174,7 @@ Open the ABP Studio UI, stop the application if it is running, build and run it
Here, find the `OrderReporting` API and execute it as shown above. You should get the order objects with product names.
Alternatively, you can visit the `/api/app/order-reporting/latest-orders` URL to directly execute the HTTP API on the browser (you should write the full URL, like `https://localhost:44358/api/app/order-reporting/latest-orders` - port can be different for your case)
Alternatively, you can visit the `/api/app/order-reporting/latest-orders` URL to directly execute the HTTP API on the browser (you should write the full URL, like `https://localhost:44303/api/app/order-reporting/latest-orders` - port can be different for your case)
## Summary
@ -190,7 +188,7 @@ Now, you know the fundamental principles and mechanics of building sophisticated
## Download the Source Code
You can download the completed sample solution [here](https://github.com/abpframework/abp-samples/tree/master/ModularCRM).
You can download the completed sample solution [here](https://github.com/abpframework/abp-samples/tree/master/ModularCrm).