Browse Source

Add note and revisit best practice guides

pull/21711/head
Halil İbrahim Kalkan 2 years ago
parent
commit
63d3d6eb80
  1. 44
      docs/en/framework/architecture/best-practices/application-services.md
  2. 6
      docs/en/framework/architecture/best-practices/data-transfer-objects.md
  3. 10
      docs/en/framework/architecture/best-practices/domain-services.md
  4. 33
      docs/en/framework/architecture/best-practices/entities.md
  5. 18
      docs/en/framework/architecture/best-practices/entity-framework-core-integration.md
  6. 4
      docs/en/framework/architecture/best-practices/module-architecture.md
  7. 18
      docs/en/framework/architecture/best-practices/mongodb-integration.md
  8. 10
      docs/en/framework/architecture/best-practices/repositories.md
  9. 2
      docs/en/framework/architecture/domain-driven-design/data-transfer-objects.md
  10. 2
      docs/en/framework/architecture/domain-driven-design/domain-services.md

44
docs/en/framework/architecture/best-practices/application-services.md

@ -1,8 +1,14 @@
# 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.
@ -117,7 +123,7 @@ Task<QuestionWithDetailsDto> GetAsync(Guid id);
Task<List<QuestionWithDetailsDto>> GetListAsync(QuestionListQueryDto queryDto);
````
##### Creating A New Entity
#### Creating A New Entity
* **Do** use the `CreateAsync` **method name**.
* **Do** get a **specialized input** DTO to create the entity.
@ -151,7 +157,7 @@ public class CreateQuestionDto : ExtensibleObject
}
````
##### Updating An Existing Entity
#### Updating An Existing Entity
- **Do** use the `UpdateAsync` **method name**.
- **Do** get a **specialized input** DTO to update the entity.
@ -167,7 +173,7 @@ Example:
Task<QuestionWithDetailsDto> UpdateAsync(Guid id, UpdateQuestionDto updateQuestionDto);
````
##### Deleting An Existing Entity
#### Deleting An Existing Entity
- **Do** use the `DeleteAsync` **method name**.
- **Do** get Id with a **primitive** method parameter. Example:
@ -176,7 +182,7 @@ Task<QuestionWithDetailsDto> UpdateAsync(Guid id, UpdateQuestionDto updateQuesti
Task DeleteAsync(Guid id);
````
##### Other Methods
#### Other Methods
* **Can** define additional methods to perform operations on the entity. Example:
@ -186,7 +192,7 @@ Task<int> VoteAsync(Guid id, VoteType type);
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;
* Use domain layer to perform the required task.

6
docs/en/framework/architecture/best-practices/data-transfer-objects.md

@ -1,5 +1,11 @@
# 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.

10
docs/en/framework/architecture/best-practices/domain-services.md

@ -1,6 +1,10 @@
# Domain Services Best Practices & Conventions
### Domain Service
> 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.
@ -57,8 +61,6 @@ public async Task AssignToAsync(Issue issue, IdentityUser user)
- **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.
## See Also
* [Video tutorial](https://abp.io/video-courses/essentials/domain-services)

33
docs/en/framework/architecture/best-practices/entities.md

@ -1,12 +1,16 @@
# Entity Best Practices & Conventions
### Entities
> 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
}
````
### References
## References
* Effective Aggregate Design by Vaughn Vernon
http://dddcommunity.org/library/vernon_2011
## See Also
## See Also
* [Video tutorial](https://abp.io/video-courses/essentials/entities)

18
docs/en/framework/architecture/best-practices/entity-framework-core-integration.md

@ -1,12 +1,16 @@
# Entity Framework Core Integration Best Practices
> See [Entity Framework Core Integration document](../../data/entity-framework-core) for the basics of the EF Core integration.
> This document offers best practices for implementing Entity Framework Core integration in your modules and applications.
>
> **Ensure you've read the [*Entity Framework Core Integration*](../../data/entity-framework-core/index.md) document first.**
## General
- **Do** define a separated `DbContext` interface and class for each module.
- **Do not** rely on lazy loading on the application development.
- **Do not** enable lazy loading for the `DbContext`.
### DbContext Interface
## DbContext Interface
- **Do** define an **interface** for the `DbContext` that inherits from `IEfCoreDbContext`.
- **Do** add a `ConnectionStringName` **attribute** to the `DbContext` interface.
@ -23,7 +27,7 @@ public interface IIdentityDbContext : IEfCoreDbContext
* **Do not** define `set;` for the properties in this interface.
### DbContext class
## DbContext class
* **Do** inherit the `DbContext` from the `AbpDbContext<TDbContext>` class.
* **Do** add a `ConnectionStringName` attribute to the `DbContext` class.
@ -46,7 +50,7 @@ public class IdentityDbContext : AbpDbContext<IdentityDbContext>, IIdentityDbCon
}
````
### Table Prefix and Schema
## Table Prefix and Schema
- **Do** add static `TablePrefix` and `Schema` **properties** to the `DbContext` class. Set default value from a constant. Example:
@ -58,7 +62,7 @@ public static string Schema { get; set; } = AbpIdentityConsts.DefaultDbSchema;
- **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.

4
docs/en/framework/architecture/best-practices/module-architecture.md

@ -1,13 +1,13 @@
# Module Architecture Best Practices & Conventions
### Solution Structure
## Solution Structure
* **Do** create a separated Visual Studio solution for every module.
* **Do** name the solution as *CompanyName.ModuleName* (for core ABP modules, it's *Volo.Abp.ModuleName*).
* **Do** develop the module as layered, so it has several packages (projects) those are related to each other.
* Every package has its own module definition file and explicitly declares the dependencies for the depended packages/modules.
### Layers & Packages
## Layers & Packages
The following diagram shows the packages of a well-layered module and dependencies of those packages between them:

18
docs/en/framework/architecture/best-practices/mongodb-integration.md

@ -1,8 +1,14 @@
# MongoDB Integration
> 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.

10
docs/en/framework/architecture/best-practices/repositories.md

@ -1,6 +1,10 @@
# Repository Best Practices & Conventions
### Repository Interfaces
> 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:
@ -68,7 +72,7 @@ Task<List<IdentityUser>> GetListByNormalizedRoleNameAsync(
* **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.
### See Also
## See Also
* [Entity Framework Core Integration](./entity-framework-core-integration.md)
* [MongoDB Integration](./mongodb-integration.md)

2
docs/en/framework/architecture/domain-driven-design/data-transfer-objects.md

@ -1,7 +1,5 @@
# Data Transfer Objects
## Introduction
**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.

2
docs/en/framework/architecture/domain-driven-design/domain-services.md

@ -1,7 +1,5 @@
# Domain Services
## Introduction
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).

Loading…
Cancel
Save