From f3b5fab7e062ead02bb654763cf77bc223b87952 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 23 Nov 2020 15:14:21 +0300 Subject: [PATCH 1/5] #5304 Added Retry/Failure Logic & Polly Integration section to the Dynamic-CSharp-API-Clients document. --- docs/en/API/Dynamic-CSharp-API-Clients.md | 51 ++++++++++++++++++++--- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/docs/en/API/Dynamic-CSharp-API-Clients.md b/docs/en/API/Dynamic-CSharp-API-Clients.md index d92846f5e8..b1d71e0bb1 100644 --- a/docs/en/API/Dynamic-CSharp-API-Clients.md +++ b/docs/en/API/Dynamic-CSharp-API-Clients.md @@ -1,10 +1,23 @@ # Dynamic C# API Client Proxies -ABP can dynamically create C# API client proxies to call remote HTTP services (REST APIs). In this way, you don't need to deal with `HttpClient` and other low level HTTP features to call remote services and get results. +ABP can dynamically create C# API client proxies to call your remote HTTP services (REST APIs). In this way, you don't need to deal with `HttpClient` and other low level details to call remote services and get results. + +Dynamic C# proxies automatically handles the following stuff for you; + +* Maps C# **method calls** to remote server **HTTP calls** by considering the HTTP method, route, query string parameters, request payload and other details. +* **Authenticates** the HTTP Client by adding access token to the HTTP header. +* **Serializes** to and deserialize from JSON. +* Handles HTTP API **versioning**. +* Add **correlation id**, current **tenant** id and the current **culture** to the request. +* Properly **handles the error messages** sent by the server and throws proper exceptions. + +This system can be used by any type of .NET client to consume your HTTP APIs. ## Service Interface -Your service/controller should implement an interface that is shared between the server and the client. So, first define a service interface in a shared library project. Example: +Your service/controller should implement an interface that is shared between the server and the client. So, first define a service interface in a shared library project, typically in the `Application.Contracts` project if you've created your solution using the startup templates. + +Example: ````csharp public interface IBookAppService : IApplicationService @@ -13,7 +26,7 @@ public interface IBookAppService : IApplicationService } ```` -Your interface should implement the `IRemoteService` interface to be automatically discovered. Since the `IApplicationService` inherits the `IRemoteService` interface, the `IBookAppService` above satisfies this condition. +> Your interface should implement the `IRemoteService` interface to be automatically discovered. Since the `IApplicationService` inherits the `IRemoteService` interface, the `IBookAppService` above satisfies this condition. Implement this class in your service application. You can use [auto API controller system](Auto-API-Controllers.md) to expose the service as a REST API endpoint. @@ -55,6 +68,8 @@ public class MyClientAppModule : AbpModule `AddHttpClientProxies` method gets an assembly, finds all service interfaces in the given assembly, creates and registers proxy classes. +> The startup templates already comes pre-configured for the client proxy generation, in the `HttpApi.Client` project. + ### Endpoint Configuration `RemoteServices` section in the `appsettings.json` file is used to get remote service address by default. Simplest configuration is shown below: @@ -106,7 +121,7 @@ While you can inject `IBookAppService` like above to use the client proxy, you c ### AbpRemoteServiceOptions -`AbpRemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can use `Configure` method to set or override it. Example: +`AbpRemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can configure it in the `ConfigureServices` method of your [module](../Module-Development-Basics.md) to set or override it. Example: ````csharp public override void ConfigureServices(ServiceConfigurationContext context) @@ -162,4 +177,30 @@ context.Services.AddHttpClientProxies( Using `asDefaultServices: false` may only be needed if your application has already an implementation of the service and you do not want to override/replace the other implementation by your client proxy. -> If you disable `asDefaultServices`, you can only use `IHttpClientProxy` interface to use the client proxies. See the *IHttpClientProxy Interface* section above. \ No newline at end of file +> If you disable `asDefaultServices`, you can only use `IHttpClientProxy` interface to use the client proxies. See the *IHttpClientProxy Interface* section above. + +### Retry/Failure Logic & Polly Integration + +If you want to add retry logic for the failing remote HTTP calls for the client proxies, you can configure the `AbpHttpClientBuilderOptions` in the `PreConfigureServices` method of your module class. + +**Example: Use the [Polly](https://github.com/App-vNext/Polly) library to re-try 3 times on a failure** + +````csharp +public override void PreConfigureServices(ServiceConfigurationContext context) +{ + PreConfigure(options => + { + options.ProxyClientBuildActions.Add((remoteServiceName, clientBuilder) => + { + clientBuilder.AddTransientHttpErrorPolicy(policyBuilder => + policyBuilder.WaitAndRetryAsync( + 3, + i => TimeSpan.FromSeconds(Math.Pow(2, i)) + ) + ); + }); + }); +} +```` + +This example uses the [Microsoft.Extensions.Http.Polly](https://www.nuget.org/packages/Microsoft.Extensions.Http.Polly) package. You also need to import the `Polly` namespace (`using Polly;`) to be able to use the `WaitAndRetryAsync` method. \ No newline at end of file From be29aece0727bb3c50aa244ef778ebd0626d3531 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 23 Nov 2020 15:49:58 +0300 Subject: [PATCH 2/5] Resolved#6297: Require ExposeServicesAttribute to remove the duplicated controller from the application model. --- .../Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs | 9 +++++++-- .../Mvc/Conventions/AbpServiceConvention.cs | 11 +++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs index 55d5512c6a..d9af39476d 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcOptions.cs @@ -1,4 +1,6 @@ -using Volo.Abp.AspNetCore.Mvc.Conventions; +using System; +using System.Collections.Generic; +using Volo.Abp.AspNetCore.Mvc.Conventions; namespace Volo.Abp.AspNetCore.Mvc { @@ -8,9 +10,12 @@ namespace Volo.Abp.AspNetCore.Mvc public AbpConventionalControllerOptions ConventionalControllers { get; } + public HashSet IgnoredControllersOnModelExclusion { get; } + public AbpAspNetCoreMvcOptions() { ConventionalControllers = new AbpConventionalControllerOptions(); + IgnoredControllersOnModelExclusion = new HashSet(); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs index 3198e73e45..4d1490ec40 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Conventions/AbpServiceConvention.cs @@ -77,10 +77,21 @@ namespace Volo.Abp.AspNetCore.Mvc.Conventions foreach (var controllerModel in application.Controllers) { + if (!controllerModel.ControllerType.IsDefined(typeof(ExposeServicesAttribute), false)) + { + continue; + } + + if (Options.IgnoredControllersOnModelExclusion.Contains(controllerModel.ControllerType)) + { + continue; + } + var baseControllerTypes = controllerModel.ControllerType .GetBaseClasses(typeof(Controller), includeObject: false) .Where(t => !t.IsAbstract) .ToArray(); + if (baseControllerTypes.Length > 0) { derivedControllerModels.Add(controllerModel); From fb764897d51d2b22dab598a2be251e08f2cec3fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 23 Nov 2020 21:10:25 +0300 Subject: [PATCH 3/5] Added layers. --- ...main-Driven-Design-Implementation-Guide.md | 34 ++++++++++++++++-- .../domain-driven-design-vs-solution.png | Bin 0 -> 34798 bytes 2 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 docs/en/images/domain-driven-design-vs-solution.png diff --git a/docs/en/Domain-Driven-Design-Implementation-Guide.md b/docs/en/Domain-Driven-Design-Implementation-Guide.md index b0a9692f83..ddf67db4e5 100644 --- a/docs/en/Domain-Driven-Design-Implementation-Guide.md +++ b/docs/en/Domain-Driven-Design-Implementation-Guide.md @@ -2,9 +2,11 @@ ## Introduction +This is a **practical guide** for implementing the Domain Driven Design (DDD). While the implementation details relies on the ABP Framework infrastructure, core concepts, principles and patterns are applicable in any kind of solution, even if it is not a .NET solution. + ### Goals -This is an **integrative guide** for implementing the Domain Driven Design (DDD). The goals of this document are; +The goals of this document are; * **Introduce and explain** the DDD architecture, concepts, principles, patterns and building blocks. * Explain the **layered architecture** & solution structure offered by the ABP Framework. @@ -69,7 +71,7 @@ This section introduces the essential building blocks of the Domain & Applicatio * **Entity**: An [Entity](Entities.md) is an object with its own properties (state, data) and methods that implements the business logic that is executed on these properties. An entity is represented by its unique identifier (Id). Two entity object with different Ids are considered as different entities. * **Value Object**: A [Value Object](Value-Objects.md) is another kind of domain object that is identified by its properties rather than a unique Id. That means two Value Objects with same properties are considered as the same object. Value objects are generally implemented as immutable and mostly are much simpler than the Entities. * **Aggregate & Aggregate Root**: An [Aggregate](Entities.md) is a cluster of objects (entities and value objects) bound together by an **Aggregate Root** object. The Aggregate Root is a specific type of an entity with some additional responsibilities. -* **Repository**: A [Repository](Repositories.md) is a collection-like interface that is used by the Domain and Application Layers to access to the data persistence system (the database). It hides the complexity of the DBMS from the business code. +* **Repository** (interface): A [Repository](Repositories.md) is a collection-like interface that is used by the Domain and Application Layers to access to the data persistence system (the database). It hides the complexity of the DBMS from the business code. Domain Layer contains the `interface`s of the repositories. * **Domain Service**: A [Domain Service](Domain-Services.md) is a stateless service that implements core business rules of the domain. It is useful to implement domain logic that depends on multiple aggregate (entity) type or some external services. * **Specification**: A [Specification](Specifications.md) is used to define named, reusable and combinable filters for entities and other business objects. * **Domain Event**: A [Domain Event](Event-Bus.md) is a way of informing other services in a loosely coupled manner, when a domain specific event occurs. @@ -84,6 +86,34 @@ This section introduces the essential building blocks of the Domain & Applicatio ### Layering of a .NET Solution +The picture below shows a Visual Studio Solution created using the ABP's [application startup template](Startup-Templates/Application.md): + +![domain-driven-design-vs-solution](images/domain-driven-design-vs-solution.png) + +The solution name is `IssueTracking` and it consists of multiple projects. The solution is layered by considering **DDD principles** as well as **development** and **deployment** practicals. The sub sections below explains the projects in the solution; + +#### The Domain Layer + +The Domain Layer is splitted into two projects; + +* `IssueTracking.Domain` is the **essential domain layer** that contains all the **building blocks** (entities, value objects, domain services, specifications, repository interfaces, etc.) introduced before. +* `IssueTracking.Domain.Shared` is a thin project that contains some types those belong to the Domain Layer, but shared with all other layers. For example, it may contain some constants and `enum`s related to the Domain Objects but need to be **reused by other layers**. + +#### The Application Layer + +The Application Layer is also splitted into two projects; + +* `IssueTracking.Application.Contracts` contains the application service **interfaces** and the **DTO**s used by these interfaces. This project can be shared by the client applications (including the UI). +* `IssueTracking.Application` is the **essential application layer** that **implements** the interfaces defined in the Contracts project. + +#### The Presentation Layer + +`IssueTracking.Web` is an ASP.NET Core MVC / Razor Pages application for this example. + +> ABP Framework also supports different kind of UI frameworks including [Angular](UI/Angular/Quick-Start.md) and [Blazor](UI/Blazor/Overall.md). In these cases, the `IssueTracking.Web` doesn't exist in the solution. Instead, an `IssueTracking.HttpApi.Host` application will be in the solution to serve the HTTP APIs as a standalone endpoint to be consumed by the UI applications via HTTP API calls. + +#### The Infrastructure Layer + TODO ### Execution Flow a DDD Based Application diff --git a/docs/en/images/domain-driven-design-vs-solution.png b/docs/en/images/domain-driven-design-vs-solution.png new file mode 100644 index 0000000000000000000000000000000000000000..ef0f51a4189e55f3a6050155142495aff1851b7a GIT binary patch literal 34798 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%0Den|g;nB=mD_?&HeS@X^CF=N* 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 literal 0 HcmV?d00001 From b34bc27461762fc1c763aeb4bb0585b41be601b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 23 Nov 2020 21:52:16 +0300 Subject: [PATCH 4/5] Explained layers of the startup solution. --- ...main-Driven-Design-Implementation-Guide.md | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/docs/en/Domain-Driven-Design-Implementation-Guide.md b/docs/en/Domain-Driven-Design-Implementation-Guide.md index ddf67db4e5..5f1e49c557 100644 --- a/docs/en/Domain-Driven-Design-Implementation-Guide.md +++ b/docs/en/Domain-Driven-Design-Implementation-Guide.md @@ -108,13 +108,36 @@ The Application Layer is also splitted into two projects; #### The Presentation Layer -`IssueTracking.Web` is an ASP.NET Core MVC / Razor Pages application for this example. +* `IssueTracking.Web` is an ASP.NET Core MVC / Razor Pages application for this example. > ABP Framework also supports different kind of UI frameworks including [Angular](UI/Angular/Quick-Start.md) and [Blazor](UI/Blazor/Overall.md). In these cases, the `IssueTracking.Web` doesn't exist in the solution. Instead, an `IssueTracking.HttpApi.Host` application will be in the solution to serve the HTTP APIs as a standalone endpoint to be consumed by the UI applications via HTTP API calls. +#### The Remote Service Layer + +* `IssueTracking.HttpApi` project contains HTTP APIs defined by the solution. It typically contains MVC `Controller`s and related models, if available. So, you write your HTTP APIs in this project. + +> Most of the time, API Controllers are just wrappers around the Application Services to expose them to the remote clients. Since ABP Framework's [Automatic API Controller System](API/Auto-API-Controllers.md) **automatically configures and exposes your Application Services as API Controllers**, you typically don't create Controllers in this project. However, the startup solution includes it for the cases you need to manually create API controllers. + +* `IssueTracking.HttpApi.Client` project is useful when you have a C# application that needs to consume your HTTP APIs. Once the client application references this project, it can directly [inject](Dependency-Injection.md) & use the Application Services. This is possible by the help of the ABP Framework's [Dynamic C# Client API Proxies System](API/Dynamic-CSharp-API-Clients.md). + +> There is a Console Application in `test` folder the solution, named `IssueTracking.HttpApi.Client.ConsoleTestApp`. It simply uses the `IssueTracking.HttpApi.Client` project to consume the APIs exposed by the application. It is just a demo application and you can safely delete it. You can even delete the `IssueTracking.HttpApi.Client` project if you think that you don't need to them. + #### The Infrastructure Layer -TODO +In a DDD implementation, you may have a single Infrastructure project to implement all the abstractions and integrations, or you may have different projects for each dependency. + +We suggest a balanced approach; Create separate projects for main infrastructure dependencies (like Entity Framework Core) and a common infrastructure project for other infrastructure. + +ABP's startup solution has two projects for the Entity Framework Core integration; + +* `IssueTracking.EntityFrameworkCore` is the essential integration package for the EF Core. Your application's `DbContext`, database mappings and other EF Core related stuff are located here. +* `IssueTracking.EntityFrameworkCore.DbMigrations` is a special project to manage the Code First database migrations. There is a separate `DbContext` in this project to track the migrations. You typically don't touch this project much except you need to create a new database migration or add an [application module](Modules/Index.md) that has some database tables and naturally requires to create a new database migration. + +> You may wonder why there are two projects for the EF Core. It is mostly related to [modularity](Module-Development-Basics.md). Each module has its own independent `DbContext` and your application has also one `DbContext`. `DbMigrations` project contains a **union** of the modules to track and apply a **single migration path**. While most of the times you don't need to know it, you can see the [EF Core migrations](Entity-Framework-Core-Migrations.md) document for more information. + +#### Other Projects + +There is one more project, `IssueTracking.DbMigrator`, that is a simple Console Application that **migrates** the database schema and **[seeds](Data-Seeding.md) the initial** data when you execute it. It is a useful **utility application** that you can use it in development as well as in production environment. ### Execution Flow a DDD Based Application From 58cd7a4f11ccd04c9d2b238d2cb23ecb1053e4ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 23 Nov 2020 21:59:49 +0300 Subject: [PATCH 5/5] Update Domain-Driven-Design-Implementation-Guide.md --- docs/en/Domain-Driven-Design-Implementation-Guide.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/en/Domain-Driven-Design-Implementation-Guide.md b/docs/en/Domain-Driven-Design-Implementation-Guide.md index 5f1e49c557..0d001c0b58 100644 --- a/docs/en/Domain-Driven-Design-Implementation-Guide.md +++ b/docs/en/Domain-Driven-Design-Implementation-Guide.md @@ -139,6 +139,10 @@ ABP's startup solution has two projects for the Entity Framework Core integratio There is one more project, `IssueTracking.DbMigrator`, that is a simple Console Application that **migrates** the database schema and **[seeds](Data-Seeding.md) the initial** data when you execute it. It is a useful **utility application** that you can use it in development as well as in production environment. +### Dependencies of the Projects in the Solution + +TODO + ### Execution Flow a DDD Based Application TODO