From 489fc642555202dd9a0248493c39d44f4914070e Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Mon, 18 Feb 2019 10:23:00 +0300 Subject: [PATCH 01/10] Completed Backend Admin gateway section. --- docs/en/Samples/Microservice-Demo.md | 44 ++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 3 deletions(-) diff --git a/docs/en/Samples/Microservice-Demo.md b/docs/en/Samples/Microservice-Demo.md index e4e61b6f05..3c03df63e5 100644 --- a/docs/en/Samples/Microservice-Demo.md +++ b/docs/en/Samples/Microservice-Demo.md @@ -559,11 +559,49 @@ Ocelot needs to know the real URLs of the microservices to be able to redirect H `ReRoutes` is an array of URL mappings. `BaseUrl` in the `GlobalConfiguration` section is the URL of this gateway (Ocelot needs to know its own URL). See [its own documentation](https://ocelot.readthedocs.io/en/latest/features/configuration.html) to better understand the configuration. -TODO... +Ocelot is a finalizer ASP.NET Core middleware and should be written as the last item in the pipeline: -Swagger +````csharp +app.UseOcelot().Wait(); +```` -TODO +It handles and redirects requests based on the configuration above. + +#### ABP Configuration Endpoints + +ABP provides some built-in APIs to get some configuration and information from the server. Examples: + +* `/api/abp/application-configuration` returns localization texts, permission and setting values (try http://localhost:65115/api/abp/application-configuration for this gateway). +* `/Abp/ServiceProxyScript` returns dynamic javascript proxies to call services from a javascript client (try http://localhost:65115/Abp/ServiceProxyScript for this gateway). + +These endpoints should be served by the gateway service, not by microservices. A microservice can only know permissions related to that microservice. But, once properly configured, gateway can aggregate permission values for multiple services as a single list which is more suitable for clients. + +For this purpose, the ASP.NET Core pipeline was configured to handle some specific routes via MVC, instead of Ocelot. To make this possible, MapWhen extension method is used like that: + +````csharp +app.MapWhen(ctx => ctx.Request.Path.ToString().StartsWith("/api/abp/") || + ctx.Request.Path.ToString().StartsWith("/Abp/"), + app2 => + { + app2.UseMvcWithDefaultRouteAndArea(); + }); + +app.UseOcelot().Wait(); +```` + +This configuration uses standard MVC middleware when request path starts with `/api/abp/` or `/Abp/`. + +#### Swagger + +This gateway is configured to use the [swagger UI](https://swagger.io/tools/swagger-ui/), a popular tool to discover & test HTTP APIs. Normally, Ocelot does not support to show APIs on the swagger, because it can not know details of each microservice API. But it is possible when you follow ABP layered module architecture [best practices](../Best-Practices/Index.md). + +`BackendAdminAppGatewayHostModule` adds dependency to `AbpIdentityHttpApiModule` (*[Volo.Abp.Identity.HttpApi](https://www.nuget.org/packages/Volo.Abp.Identity.HttpApi)* package) and `ProductManagementHttpApiModule` (*ProductManagement.HttpApi* project) to include their HTTP API Controllers. In this way, swagger can discover them. While it references to the API layer, it does not reference to the implementation of application services, because they will be running in the related microservice endpoints and redirected by the Ocelot based on the request URL. + +Anyway, when you open the URL `http://localhost:65115/swagger/index.html`, you will see APIs of all configured microservices. + +#### Permission Management + +Backend Admin Application provides a permission management UI (seen before) and uses this gateway to get/set permissions. Permission management API is hosted inside the gateway, instead of a separate service. This is a design decision, but it could be hosted as another microservice if you would like. #### Other Dependencies From a020ec967477fec00913ea0a9e362cb30b7d9ba0 Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Mon, 18 Feb 2019 10:31:41 +0300 Subject: [PATCH 02/10] Added Public Web Site Gateway section --- docs/en/Samples/Microservice-Demo.md | 99 ++++++++++++++++++++++++++-- 1 file changed, 94 insertions(+), 5 deletions(-) diff --git a/docs/en/Samples/Microservice-Demo.md b/docs/en/Samples/Microservice-Demo.md index 3c03df63e5..6f4b8a1aa3 100644 --- a/docs/en/Samples/Microservice-Demo.md +++ b/docs/en/Samples/Microservice-Demo.md @@ -216,7 +216,7 @@ It also provides Login & Register pages: These pages are not included in the project itself. Instead, AuthServer project uses the prebuilt ABP [account module](https://github.com/abpframework/abp/tree/master/modules/account) with IdentityServer extension. That means it can also act as an OpenId Connect server with necessary UI and logic. -#### Other Dependencies +#### Dependencies * **RabbitMQ** for messaging to other services. * **Redis** for distributed/shared caching. @@ -317,7 +317,7 @@ Once you refer these client packages, you can directly inject an application ser Since microservices requires authentication & authorization, each remote service call should contain an Authentication header. This header is obtained from the `access_token` inside the current `HttpContext` for the current user. This is automatically done when you use the `Volo.Abp.Http.Client.IdentityModel` package. `BackendAdminAppHostModule` declares dependencies to this package and to the related `AbpHttpClientIdentityModelModule` class. It is integrated to the HTTP Clients explained above. -#### Other Dependencies +#### Dependencies - **Redis** for distributed/shared caching. - **Elasticsearch** for storing logs. @@ -409,7 +409,7 @@ Publc web site application uses the Blogging and Product microservices for all o Just like explained in the Backend Admin Application section, Public Web Site project also uses the `AbpHttpClientIdentityModelModule` to pass `access_token` to the calling services for authentication. -#### Other Dependencies +#### Dependencies - **Redis** for distributed/shared caching. - **Elasticsearch** for storing logs. @@ -512,7 +512,7 @@ context.Services.AddAuthentication("Bearer") `AddIdentityServerAuthentication` extension method comes from the [IdentityServer4.AccessTokenValidation](https://www.nuget.org/packages/IdentityServer4.AccessTokenValidation) package, part of the IdentityServer4 project (see [its documentation](http://docs.identityserver.io/en/latest/topics/apis.html)). -`ApiName` is the API which is being protected, `BackendAdminAppGateway` in this case. So, this solution defines gateways as APIs too. Rest of the configuration is related to claims mapping (which is planned to be automated in next ABP versions). The configuration related to authentication in the `appsettings.json` is simple: +`ApiName` is the API which is being protected, `BackendAdminAppGateway` in this case. So, this solution defines gateways as API resources. Rest of the configuration is related to claims mapping (which is planned to be automated in next ABP versions). The configuration related to authentication in the `appsettings.json` is simple: ````json "AuthServer": { @@ -603,12 +603,101 @@ Anyway, when you open the URL `http://localhost:65115/swagger/index.html`, you w Backend Admin Application provides a permission management UI (seen before) and uses this gateway to get/set permissions. Permission management API is hosted inside the gateway, instead of a separate service. This is a design decision, but it could be hosted as another microservice if you would like. -#### Other Dependencies +#### Dependencies - **RabbitMQ** for messaging to other services. - **Redis** for distributed/shared caching. - **Elasticsearch** for storing logs. +### Public Web Site Gateway (PublicWebSiteGateway.Host) + +This is backend (server side API gateway) for the "Public Web Site" application. + +#### Authentication + +This gateway uses IdentityServer `Bearer` authentication and configured like that: + +```csharp +context.Services.AddAuthentication("Bearer") +.AddIdentityServerAuthentication(options => +{ + options.Authority = configuration["AuthServer:Authority"]; + options.ApiName = configuration["AuthServer:ApiName"]; + options.RequireHttpsMetadata = false; + options.InboundJwtClaimTypeMap["sub"] = AbpClaimTypes.UserId; + options.InboundJwtClaimTypeMap["role"] = AbpClaimTypes.Role; + options.InboundJwtClaimTypeMap["email"] = AbpClaimTypes.Email; + options.InboundJwtClaimTypeMap["email_verified"] = AbpClaimTypes.EmailVerified; + options.InboundJwtClaimTypeMap["phone_number"] = AbpClaimTypes.PhoneNumber; + options.InboundJwtClaimTypeMap["phone_number_verified"] = + AbpClaimTypes.PhoneNumberVerified; + options.InboundJwtClaimTypeMap["name"] = AbpClaimTypes.UserName; +}); +``` + +`AddIdentityServerAuthentication` extension method comes from the [IdentityServer4.AccessTokenValidation](https://www.nuget.org/packages/IdentityServer4.AccessTokenValidation) package, part of the IdentityServer4 project (see [its documentation](http://docs.identityserver.io/en/latest/topics/apis.html)). + +`ApiName` is the API which is being protected, `PublicWebSiteGateway` in this case. Rest of the configuration is related to claims mapping (which is planned to be automated in next ABP versions). The configuration related to authentication in the `appsettings.json` is simple: + +```json +"AuthServer": { + "Authority": "http://localhost:64999", + "ApiName": "PublicWebSiteGateway" +} +``` + +#### Ocelot Configuration + +Ocelot needs to know the real URLs of the microservices to be able to redirect HTTP requests. The configuration for this gateway is like below: + +```json +"ReRoutes": [ + { + "DownstreamPathTemplate": "/api/productManagement/{everything}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 60244 + } + ], + "UpstreamPathTemplate": "/api/productManagement/{everything}", + "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] + }, + { + "DownstreamPathTemplate": "/api/blogging/{everything}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 62157 + } + ], + "UpstreamPathTemplate": "/api/blogging/{everything}", + "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] + } +], +"GlobalConfiguration": { + "BaseUrl": "http://localhost:64897" +} +``` + +See [its own documentation](https://ocelot.readthedocs.io/en/latest/features/configuration.html) to better understand the Ocelot configuration. + +#### Other + +See the "ABP Configuration Endpoints" and "Swagger" topics inside the "Backend Admin Application Gateway" section which are very similar for this gateway. + +#### Dependencies + +- **RabbitMQ** for messaging to other services. +- **Redis** for distributed/shared caching. +- **Elasticsearch** for storing logs. + +### Internal Gateway (InternalGateway.Host) + +TODO + ## Microservices ### Identity Service From 571b9172e4cc513e51959980a4aecfa3fdb72029 Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Mon, 18 Feb 2019 10:44:25 +0300 Subject: [PATCH 03/10] Add Blogging.HttpApi to InternalGatewayHostModule --- .../gateways/InternalGateway.Host/InternalGateway.Host.csproj | 1 + .../gateways/InternalGateway.Host/InternalGatewayHostModule.cs | 2 ++ 2 files changed, 3 insertions(+) diff --git a/samples/MicroserviceDemo/gateways/InternalGateway.Host/InternalGateway.Host.csproj b/samples/MicroserviceDemo/gateways/InternalGateway.Host/InternalGateway.Host.csproj index 0428736a75..80b4b6ebb0 100644 --- a/samples/MicroserviceDemo/gateways/InternalGateway.Host/InternalGateway.Host.csproj +++ b/samples/MicroserviceDemo/gateways/InternalGateway.Host/InternalGateway.Host.csproj @@ -27,6 +27,7 @@ + diff --git a/samples/MicroserviceDemo/gateways/InternalGateway.Host/InternalGatewayHostModule.cs b/samples/MicroserviceDemo/gateways/InternalGateway.Host/InternalGatewayHostModule.cs index a418aabf79..943eaf0fc9 100644 --- a/samples/MicroserviceDemo/gateways/InternalGateway.Host/InternalGatewayHostModule.cs +++ b/samples/MicroserviceDemo/gateways/InternalGateway.Host/InternalGatewayHostModule.cs @@ -15,12 +15,14 @@ using Volo.Abp.Modularity; using Volo.Abp.PermissionManagement.EntityFrameworkCore; using Volo.Abp.Security.Claims; using Volo.Abp.SettingManagement.EntityFrameworkCore; +using Volo.Blogging; namespace InternalGateway.Host { [DependsOn( typeof(AbpAutofacModule), typeof(AbpIdentityHttpApiModule), + typeof(BloggingHttpApiModule), typeof(ProductManagementHttpApiModule), typeof(AbpEntityFrameworkCoreSqlServerModule), typeof(AbpPermissionManagementEntityFrameworkCoreModule), From bc4cd75a12fbb0ce4d1c45c90cd1715a774f530d Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Mon, 18 Feb 2019 10:44:40 +0300 Subject: [PATCH 04/10] Added Internal Gateway section. --- docs/en/Samples/Microservice-Demo.md | 112 ++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 4 deletions(-) diff --git a/docs/en/Samples/Microservice-Demo.md b/docs/en/Samples/Microservice-Demo.md index 6f4b8a1aa3..45fbe14e25 100644 --- a/docs/en/Samples/Microservice-Demo.md +++ b/docs/en/Samples/Microservice-Demo.md @@ -466,7 +466,7 @@ This sample uses the `client_credentials` grant type which requires a `ClientId` Resource Owner Password requires a `UserName` & `UserPassword` in addition to client credentials. This grant type is useful to call remote services on behalf of a user. -`Scope` declares the APIs (and the gateway) to grant access. +`Scope` declares the APIs (and the gateway) to grant access. This application uses the Internal Gateway. #### HTTP Client Dependencies @@ -696,7 +696,93 @@ See the "ABP Configuration Endpoints" and "Swagger" topics inside the "Backend A ### Internal Gateway (InternalGateway.Host) -TODO +This gateway is not a BFF. It is designed for inter-microservice communication and is not exposed publicly. + +#### Authentication + +This gateway uses IdentityServer `Bearer` authentication and configured like that: + +```csharp +context.Services.AddAuthentication("Bearer") +.AddIdentityServerAuthentication(options => +{ + options.Authority = configuration["AuthServer:Authority"]; + options.ApiName = configuration["AuthServer:ApiName"]; + options.RequireHttpsMetadata = false; + options.InboundJwtClaimTypeMap["sub"] = AbpClaimTypes.UserId; + options.InboundJwtClaimTypeMap["role"] = AbpClaimTypes.Role; + options.InboundJwtClaimTypeMap["email"] = AbpClaimTypes.Email; + options.InboundJwtClaimTypeMap["email_verified"] = AbpClaimTypes.EmailVerified; + options.InboundJwtClaimTypeMap["phone_number"] = AbpClaimTypes.PhoneNumber; + options.InboundJwtClaimTypeMap["phone_number_verified"] = AbpClaimTypes.PhoneNumberVerified; + options.InboundJwtClaimTypeMap["name"] = AbpClaimTypes.UserName; +}); +``` + +`AddIdentityServerAuthentication` extension method comes from the [IdentityServer4.AccessTokenValidation](https://www.nuget.org/packages/IdentityServer4.AccessTokenValidation) package, part of the IdentityServer4 project (see [its documentation](http://docs.identityserver.io/en/latest/topics/apis.html)). + +`ApiName` is the API which is being protected, `InternalGateway` in this case. Rest of the configuration is related to claims mapping (which is planned to be automated in next ABP versions). The configuration related to authentication in the `appsettings.json` is simple: + +```json +"AuthServer": { + "Authority": "http://localhost:64999", + "ApiName": "InternalGateway" +} +``` + +#### Ocelot Configuration + +Ocelot needs to know the real URLs of the microservices to be able to redirect HTTP requests. The configuration for this gateway is like below: + +```json +"ReRoutes": [ + { + "DownstreamPathTemplate": "/api/identity/{everything}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 63568 + } + ], + "UpstreamPathTemplate": "/api/identity/{everything}", + "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] + }, + { + "DownstreamPathTemplate": "/api/productManagement/{everything}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 60244 + } + ], + "UpstreamPathTemplate": "/api/productManagement/{everything}", + "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] + }, + { + "DownstreamPathTemplate": "/api/blogging/{everything}", + "DownstreamScheme": "http", + "DownstreamHostAndPorts": [ + { + "Host": "localhost", + "Port": 62157 + } + ], + "UpstreamPathTemplate": "/api/blogging/{everything}", + "UpstreamHttpMethod": [ "Put", "Delete", "Get", "Post" ] + } +], +"GlobalConfiguration": { + "BaseUrl": "http://localhost:65129" +} +``` + +`ReRoutes` configuration covers all microservices in the system. See [its own documentation](https://ocelot.readthedocs.io/en/latest/features/configuration.html) to better understand the Ocelot configuration. + +#### Other + +See the "ABP Configuration Endpoints" and "Swagger" topics inside the "Backend Admin Application Gateway" section which are very similar for this gateway. ## Microservices @@ -704,14 +790,32 @@ TODO TODO -## Infrastructure +### Blogging Service + +TODO + +### Product Service TODO +## Infrastructure + ### Messaging +TODO + ### Caching +TODO + ### Logging -### Correlation Id \ No newline at end of file +TODO + +### Audit Logging + +TODO + +### Correlation Id + +TODO \ No newline at end of file From 2bf392ca285c4d5fa7ee6094e59b7772652131a2 Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Mon, 18 Feb 2019 11:12:28 +0300 Subject: [PATCH 05/10] Added Identity Service section. --- docs/en/Samples/Microservice-Demo.md | 71 +++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/docs/en/Samples/Microservice-Demo.md b/docs/en/Samples/Microservice-Demo.md index 45fbe14e25..d13bd9a9fb 100644 --- a/docs/en/Samples/Microservice-Demo.md +++ b/docs/en/Samples/Microservice-Demo.md @@ -784,11 +784,74 @@ Ocelot needs to know the real URLs of the microservices to be able to redirect H See the "ABP Configuration Endpoints" and "Swagger" topics inside the "Backend Admin Application Gateway" section which are very similar for this gateway. +#### Dependencies + +- **RabbitMQ** for messaging to other services. +- **Redis** for distributed/shared caching. +- **Elasticsearch** for storing logs. + ## Microservices +Microservices are standalone HTTP APIs those implement the business of the system in a distributed manner. + +* They are used by applications and other microservices through the gateways and HTTP APIs. +* They can raise or register to events in the system. +* They can communicate to each other via asynchronous messaging. + ### Identity Service -TODO +This service provides user and role management APIs. Shares the same database (MsDemo_Identity) with the AuthServer application. + +#### Identity Module + +This service actually just hosts the ABP Identity package/module. Does not include any API itself. In order to host it, adds the following dependencies: + +* `AbpIdentityHttpApiModule` (*[Volo.Abp.Identity.HttpApi](https://www.nuget.org/packages/Volo.Abp.Identity.HttpApi)* package) to provide Identity APIs. +* `AbpIdentityApplicationModule` (*[Volo.Abp.Identity.Application](https://www.nuget.org/packages/Volo.Abp.Identity.Application)* package) to host the implementation of the application and domain layers of the module. +* `AbpIdentityEntityFrameworkCoreModule` (*[Volo.Abp.Identity.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.Identity.EntityFrameworkCore)* package) to use EF Core as database API. + +See the [module architecture best practice guide](../Best-Practices/Module-Architecture) to understand the layering better. + +#### Authentication + +This microservice uses IdentityServer `Bearer` authentication and configured like that: + +```csharp +context.Services.AddAuthentication("Bearer") +.AddIdentityServerAuthentication(options => +{ + options.Authority = configuration["AuthServer:Authority"]; + options.ApiName = configuration["AuthServer:ApiName"]; + options.RequireHttpsMetadata = false; + options.InboundJwtClaimTypeMap["sub"] = AbpClaimTypes.UserId; + options.InboundJwtClaimTypeMap["role"] = AbpClaimTypes.Role; + options.InboundJwtClaimTypeMap["email"] = AbpClaimTypes.Email; + options.InboundJwtClaimTypeMap["email_verified"] = AbpClaimTypes.EmailVerified; + options.InboundJwtClaimTypeMap["phone_number"] = AbpClaimTypes.PhoneNumber; + options.InboundJwtClaimTypeMap["phone_number_verified"] = + AbpClaimTypes.PhoneNumberVerified; + options.InboundJwtClaimTypeMap["name"] = AbpClaimTypes.UserName; +}); +``` + +`ApiName` is the API which is being protected, `IdentityService` in this case. Rest of the configuration is related to claims mapping (which is planned to be automated in next ABP versions). The configuration related to authentication in the `appsettings.json` is simple: + +```json +"AuthServer": { + "Authority": "http://localhost:64999", + "ApiName": "IdentityService" +} +``` + +#### Swagger + +Swagger UI is configured and is the default page for this service. If you navigate to the URL `http://localhost:63568/`, you are redirected to the swagger page to see and test the API. + +#### Dependencies + +- **RabbitMQ** for messaging to other services. +- **Redis** for distributed/shared caching. +- **Elasticsearch** for storing logs. ### Blogging Service @@ -798,6 +861,12 @@ TODO TODO +## Modules + +### Product Management + +TODO + ## Infrastructure ### Messaging From 46983f18997894124323e85a9c07bc8667d34549 Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Mon, 18 Feb 2019 11:20:25 +0300 Subject: [PATCH 06/10] Added MongoDB package for blogging module --- nupkg/common.ps1 | 1 + 1 file changed, 1 insertion(+) diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index 6ac3b2c3d2..5a4aeb71e8 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -164,6 +164,7 @@ $projects = ( "modules/blogging/src/Volo.Blogging.Domain", "modules/blogging/src/Volo.Blogging.Domain.Shared", "modules/blogging/src/Volo.Blogging.EntityFrameworkCore", + "modules/blogging/src/Volo.Blogging.MongoDB", "modules/blogging/src/Volo.Blogging.HttpApi", "modules/blogging/src/Volo.Blogging.HttpApi.Client", "modules/blogging/src/Volo.Blogging.Web", From 6bae239fe1dc1ff86216968966207ce6eb9195c6 Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Mon, 18 Feb 2019 11:36:43 +0300 Subject: [PATCH 07/10] Added Blogging Service section --- docs/en/Samples/Microservice-Demo.md | 105 +++++++++++++++++- ...ple-blogservice-permission-in-database.png | Bin 0 -> 3304 bytes 2 files changed, 103 insertions(+), 2 deletions(-) create mode 100644 docs/en/images/microservice-sample-blogservice-permission-in-database.png diff --git a/docs/en/Samples/Microservice-Demo.md b/docs/en/Samples/Microservice-Demo.md index d13bd9a9fb..ca91a22c1a 100644 --- a/docs/en/Samples/Microservice-Demo.md +++ b/docs/en/Samples/Microservice-Demo.md @@ -800,7 +800,11 @@ Microservices are standalone HTTP APIs those implement the business of the syste ### Identity Service -This service provides user and role management APIs. Shares the same database (MsDemo_Identity) with the AuthServer application. +This service provides user and role management APIs. + +#### Database + +Shares the same database (MsDemo_Identity) with the AuthServer application. #### Identity Module @@ -855,7 +859,104 @@ Swagger UI is configured and is the default page for this service. If you naviga ### Blogging Service -TODO +This service provides the blogging API. + +#### Database + +It has a dedicated MongoDB database (MsDemo_Blogging) to store blog and posts. It also uses the MsDemo_Identity SQL database for audit logs, permissions and settings. So, there are two connection strings in the `appsettings.json` file: + +````json +"ConnectionStrings": { + "Default": "Server=localhost;Database=MsDemo_Identity;Trusted_Connection=True;MultipleActiveResultSets=true", + "Blogging": "mongodb://localhost|MsDemo_Blogging" +} +```` + +#### Blogging Module + +This service actually just hosts the ABP Blogging package/module. Does not include any API itself. In order to host it, adds the following dependencies: + +- `BloggingHttpApiModule` (*[Volo.Blogging.HttpApi](https://www.nuget.org/packages/Volo.Blogging.HttpApi)* package) to provide Blogging APIs. +- `BloggingApplicationModule` (*[Volo.Blogging.Application](https://www.nuget.org/packages/Volo.Blogging.Application)* package) to host the implementation of the application and domain layers of the module. +- `BloggingMongoDbModule` (*[Volo.Blogging.MongoDB](https://www.nuget.org/packages/Volo.Abp.Identity.EntityFrameworkCore)* package) to use MongoDB as the database. + +See the [module architecture best practice guide](../Best-Practices/Module-Architecture) to understand the layering better. + +#### Authentication + +This microservice uses IdentityServer `Bearer` authentication and configured like that: + +```csharp +context.Services.AddAuthentication("Bearer") +.AddIdentityServerAuthentication(options => +{ + options.Authority = configuration["AuthServer:Authority"]; + options.ApiName = configuration["AuthServer:ApiName"]; + options.RequireHttpsMetadata = false; + options.InboundJwtClaimTypeMap["sub"] = AbpClaimTypes.UserId; + options.InboundJwtClaimTypeMap["role"] = AbpClaimTypes.Role; + options.InboundJwtClaimTypeMap["email"] = AbpClaimTypes.Email; + options.InboundJwtClaimTypeMap["email_verified"] = AbpClaimTypes.EmailVerified; + options.InboundJwtClaimTypeMap["phone_number"] = AbpClaimTypes.PhoneNumber; + options.InboundJwtClaimTypeMap["phone_number_verified"] = + AbpClaimTypes.PhoneNumberVerified; + options.InboundJwtClaimTypeMap["name"] = AbpClaimTypes.UserName; +}); +``` + +`ApiName` is the API which is being protected, `BloggingService` in this case. Rest of the configuration is related to claims mapping (which is planned to be automated in next ABP versions). The configuration related to authentication in the `appsettings.json` is simple: + +```json +"AuthServer": { + "Authority": "http://localhost:64999", + "ApiName": "BloggingService" +} +``` + +#### IdentityServer Client + +This microservice also uses the Identity microservice API through the Internal Gateway, because it needs to query user details (username, email, phone, name and surname) in some cases. So, it is also a client for the IdentityServer and defines a section in the `appsettings.json` file for that: + +````json +"IdentityClients": { + "Default": { + "GrantType": "client_credentials", + "ClientId": "blogging-service-client", + "ClientSecret": "1q2w3e*", + "Authority": "http://localhost:64999", + "Scope": "InternalGateway IdentityService" + } +} +```` + +Since it uses the Internal Gateway, it should also configure the remote endpoint of the gateway: + +````json +"RemoteServices": { + "Default": { + "BaseUrl": "http://localhost:65129/", + "UseCurrentAccessToken": "false" + } +} +```` + +When you set `UseCurrentAccessToken` to `false`, ABP ignores the current `access_token` in the current `HttpContext` and authenticates to the AuthServer with the credentials defined above. + +Why not using the token of the current user in the current request? Because, the user may not have required permissions on the Identity module, so it can not just pass the current authentication token directly to the Identity service. In addition, some of the blog service APIs are anonymous (not requires authenticated user), so in some cases there is no "current user" in the HTTP request. For these reasons, Blogging service should be defined as a client for the Identity service with its own credentials and permissions. + +If you check the `AbpPermissionGrants` table in the `MsDemo_Identity` database, you can see the related permission for the `blogging-service-client`. + +![microservice-sample-blogservice-permission-in-database](../images/microservice-sample-blogservice-permission-in-database.png) + +#### Swagger + +Swagger UI is configured and is the default page for this service. If you navigate to the URL `http://localhost:62157/`, you are redirected to the swagger page to see and test the API. + +#### Dependencies + +- **RabbitMQ** for messaging to other services. +- **Redis** for distributed/shared caching. +- **Elasticsearch** for storing logs. ### Product Service diff --git a/docs/en/images/microservice-sample-blogservice-permission-in-database.png b/docs/en/images/microservice-sample-blogservice-permission-in-database.png new file mode 100644 index 0000000000000000000000000000000000000000..f99cd398937c184b7956ceed0fbdb64b2c220f67 GIT binary patch literal 3304 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 Cig7{! literal 0 HcmV?d00001 From ed343168185b0d9124fc6a830d87735a2e7ccc5f Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Mon, 18 Feb 2019 14:00:02 +0300 Subject: [PATCH 08/10] Fix name of the ProductServiceMigrationDbContextFactory --- ...ntextFactory.cs => ProductServiceMigrationDbContextFactory.cs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename samples/MicroserviceDemo/microservices/ProductService.Host/EntityFrameworkCore/{AuthServerDbContextFactory.cs => ProductServiceMigrationDbContextFactory.cs} (100%) diff --git a/samples/MicroserviceDemo/microservices/ProductService.Host/EntityFrameworkCore/AuthServerDbContextFactory.cs b/samples/MicroserviceDemo/microservices/ProductService.Host/EntityFrameworkCore/ProductServiceMigrationDbContextFactory.cs similarity index 100% rename from samples/MicroserviceDemo/microservices/ProductService.Host/EntityFrameworkCore/AuthServerDbContextFactory.cs rename to samples/MicroserviceDemo/microservices/ProductService.Host/EntityFrameworkCore/ProductServiceMigrationDbContextFactory.cs From 08c4be59f2791cf57cbedd24b4ab33cb135dafd4 Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Mon, 18 Feb 2019 14:00:20 +0300 Subject: [PATCH 09/10] Add Product Service to the document --- docs/en/Samples/Microservice-Demo.md | 101 +++++++++++++++++++++++++-- 1 file changed, 96 insertions(+), 5 deletions(-) diff --git a/docs/en/Samples/Microservice-Demo.md b/docs/en/Samples/Microservice-Demo.md index ca91a22c1a..810d8ea207 100644 --- a/docs/en/Samples/Microservice-Demo.md +++ b/docs/en/Samples/Microservice-Demo.md @@ -204,6 +204,8 @@ public class AuthServerDbContext : AbpDbContext In the **OnModelCreating**, you see **ConfigureX()** method calls. A module with a database schema generally declares such an extension method to configure EF Core mappings for its own entities. This is a flexible approach where you can arrange your databases and modules inside them; You can use a different database for each module, or combine some of them in a shared database. In the AuthServer project, we decided to combine multiple module schemas in a single EF Core DbContext, in a single physical database. These modules are Identity, IdentityServer, AuditLogging, PermissionManagement and SettingManagement modules. +Notice that this DbContext is only for database migrations. All modules have their own `DbContext` classes those are used in the runtime by the modules. + #### User Interface AuthServer has a simple home page that shows the current user info if the current user has logged in: @@ -798,7 +800,7 @@ Microservices are standalone HTTP APIs those implement the business of the syste * They can raise or register to events in the system. * They can communicate to each other via asynchronous messaging. -### Identity Service +### Identity Service (IdentityService.Host) This service provides user and role management APIs. @@ -857,9 +859,9 @@ Swagger UI is configured and is the default page for this service. If you naviga - **Redis** for distributed/shared caching. - **Elasticsearch** for storing logs. -### Blogging Service +### Blogging Service (BloggingService.Host) -This service provides the blogging API. +This service hosts the blogging API. #### Database @@ -958,9 +960,98 @@ Swagger UI is configured and is the default page for this service. If you naviga - **Redis** for distributed/shared caching. - **Elasticsearch** for storing logs. -### Product Service +### Product Service (ProductService.Host) -TODO +This service hosts the Product Management API. + +#### Database & EF Core Migrations + +It has a separated SQL database, named **MsDemo_ProductManagement**, for the product management module. It uses EF Core as the database provider and has a DbContext named `ProductServiceMigrationDbContext`: + +````csharp +public class ProductServiceMigrationDbContext : AbpDbContext +{ + public ProductServiceMigrationDbContext( + DbContextOptions options + ) : base(options) + { + + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + base.OnModelCreating(modelBuilder); + + modelBuilder.ConfigureProductManagement(); + } +} +```` + +Actual model configuration is done inside the `modelBuilder.ConfigureProductManagement()` extension method. This project maintains the database schema using EF Core migrations. + +Notice that this DbContext is only for database migrations. Product Management module has its own `DbContext` class that is used in the runtime (See `ProductManagementDbContext` class in the ProductManagement.EntityFrameworkCore project). + +There are two connection strings in the `appsettings.json` file: + +````json +"ConnectionStrings": { + "Default": "Server=localhost;Database=MsDemo_Identity;Trusted_Connection=True;MultipleActiveResultSets=true", + "ProductManagement": "Server=localhost;Database=MsDemo_ProductManagement;Trusted_Connection=True;MultipleActiveResultSets=true" +} +```` + +`Default` connection strings points to the MsDemo_Identity database that is used for audit logging, permission and setting stores. `ProductManagement` connection string is used by the product module. + +#### Product Module + +This service actually just hosts the Product Management module. Does not include any API itself. In order to host it, adds the following dependencies: + +- `ProductManagementHttpApiModule` to provide product management APIs. +- `ProductManagementApplicationModule` to host the implementation of the application and domain layers of the module. +- `ProductManagementEntityFrameworkCoreModule` to use EF Core as database API. + +See the [module architecture best practice guide](../Best-Practices/Module-Architecture) to understand the layering better. See the Product Management module section below for more information about this module. + +#### Authentication + +This microservice uses IdentityServer `Bearer` authentication and configured like that: + +```csharp +context.Services.AddAuthentication("Bearer") +.AddIdentityServerAuthentication(options => +{ + options.Authority = configuration["AuthServer:Authority"]; + options.ApiName = configuration["AuthServer:ApiName"]; + options.RequireHttpsMetadata = false; + options.InboundJwtClaimTypeMap["sub"] = AbpClaimTypes.UserId; + options.InboundJwtClaimTypeMap["role"] = AbpClaimTypes.Role; + options.InboundJwtClaimTypeMap["email"] = AbpClaimTypes.Email; + options.InboundJwtClaimTypeMap["email_verified"] = AbpClaimTypes.EmailVerified; + options.InboundJwtClaimTypeMap["phone_number"] = AbpClaimTypes.PhoneNumber; + options.InboundJwtClaimTypeMap["phone_number_verified"] = + AbpClaimTypes.PhoneNumberVerified; + options.InboundJwtClaimTypeMap["name"] = AbpClaimTypes.UserName; +}); +``` + +`ApiName` is the API which is being protected, `ProductService` in this case. Rest of the configuration is related to claims mapping (which is planned to be automated in next ABP versions). The configuration related to authentication in the `appsettings.json` is simple: + +```json +"AuthServer": { + "Authority": "http://localhost:64999", + "ApiName": "ProductService" +} +``` + +#### Swagger + +Swagger UI is configured and is the default page for this service. If you navigate to the URL `http://localhost:60244/`, you are redirected to the swagger page to see and test the API. + +#### Dependencies + +- **RabbitMQ** for messaging to other services. +- **Redis** for distributed/shared caching. +- **Elasticsearch** for storing logs. ## Modules From ee536c018d289a1662d0e7687eac4cdb641e3f2e Mon Sep 17 00:00:00 2001 From: Halil ibrahim Kalkan Date: Mon, 18 Feb 2019 14:47:43 +0300 Subject: [PATCH 10/10] Remove IProductRepository --- .../ProductManagement/ProductAppService.cs | 14 +++++++-- .../PublicProductAppService.cs | 8 +++-- .../ProductManagement/IProductRepository.cs | 13 -------- .../EfCoreProductRepository.cs | 30 ------------------- .../ProductAppService_Tests.cs | 7 ++--- 5 files changed, 19 insertions(+), 53 deletions(-) delete mode 100644 samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/IProductRepository.cs delete mode 100644 samples/MicroserviceDemo/modules/product/src/ProductManagement.EntityFrameworkCore/ProductManagement/EfCoreProductRepository.cs diff --git a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application/ProductManagement/ProductAppService.cs b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application/ProductManagement/ProductAppService.cs index 90d16ec7b5..c993f27f02 100644 --- a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application/ProductManagement/ProductAppService.cs +++ b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application/ProductManagement/ProductAppService.cs @@ -1,9 +1,13 @@ using System; using System.Collections.Generic; +using System.Linq; +using System.Linq.Dynamic.Core; using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; +using Microsoft.EntityFrameworkCore; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; namespace ProductManagement { @@ -11,9 +15,9 @@ namespace ProductManagement public class ProductAppService : ApplicationService, IProductAppService { private readonly ProductManager _productManager; - private readonly IProductRepository _productRepository; + private readonly IRepository _productRepository; - public ProductAppService(ProductManager productManager, IProductRepository productRepository) + public ProductAppService(ProductManager productManager, IRepository productRepository) { _productManager = productManager; _productRepository = productRepository; @@ -23,7 +27,11 @@ namespace ProductManagement { await NormalizeMaxResultCountAsync(input); - var products = await _productRepository.GetListAsync(input.Sorting, input.MaxResultCount, input.SkipCount); + var products = await _productRepository + .OrderBy(input.Sorting) + .Skip(input.SkipCount) + .Take(input.MaxResultCount) + .ToListAsync(); var totalCount = await _productRepository.GetCountAsync(); diff --git a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application/ProductManagement/PublicProductAppService.cs b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application/ProductManagement/PublicProductAppService.cs index 866cc31b34..b1f9f3fafc 100644 --- a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application/ProductManagement/PublicProductAppService.cs +++ b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Application/ProductManagement/PublicProductAppService.cs @@ -1,15 +1,17 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; namespace ProductManagement { public class PublicProductAppService : ApplicationService, IPublicProductAppService { - private readonly IProductRepository _productRepository; + private readonly IRepository _productRepository; - public PublicProductAppService(IProductRepository productRepository) + public PublicProductAppService(IRepository productRepository) { _productRepository = productRepository; } diff --git a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/IProductRepository.cs b/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/IProductRepository.cs deleted file mode 100644 index c6a1c865c1..0000000000 --- a/samples/MicroserviceDemo/modules/product/src/ProductManagement.Domain/ProductManagement/IProductRepository.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; -using Volo.Abp.Domain.Repositories; - -namespace ProductManagement -{ - public interface IProductRepository : IBasicRepository - { - Task> GetListAsync(string sorting, int maxResultCount, int skipCount); - } -} diff --git a/samples/MicroserviceDemo/modules/product/src/ProductManagement.EntityFrameworkCore/ProductManagement/EfCoreProductRepository.cs b/samples/MicroserviceDemo/modules/product/src/ProductManagement.EntityFrameworkCore/ProductManagement/EfCoreProductRepository.cs deleted file mode 100644 index c229505c36..0000000000 --- a/samples/MicroserviceDemo/modules/product/src/ProductManagement.EntityFrameworkCore/ProductManagement/EfCoreProductRepository.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using ProductManagement.EntityFrameworkCore; -using Volo.Abp.Domain.Repositories.EntityFrameworkCore; -using Volo.Abp.EntityFrameworkCore; -using System.Linq; -using System.Linq.Dynamic.Core; -using Microsoft.EntityFrameworkCore; - -namespace ProductManagement -{ - public class EfCoreProductRepository : EfCoreRepository, IProductRepository - { - public EfCoreProductRepository(IDbContextProvider dbContextProvider) - : base(dbContextProvider) - { - } - - public async Task> GetListAsync(string sorting, int maxResultCount, int skipCount) - { - // TODO: refactor sorting - var products = await DbSet.OrderBy(sorting ?? "creationTime desc") - .PageBy(skipCount, maxResultCount) - .ToListAsync(); - - return products; - } - } -} diff --git a/samples/MicroserviceDemo/modules/product/test/ProductManagement.Application.Tests/ProductManagement/ProductAppService_Tests.cs b/samples/MicroserviceDemo/modules/product/test/ProductManagement.Application.Tests/ProductManagement/ProductAppService_Tests.cs index 4964f5df98..2cdd7a0a61 100644 --- a/samples/MicroserviceDemo/modules/product/test/ProductManagement.Application.Tests/ProductManagement/ProductAppService_Tests.cs +++ b/samples/MicroserviceDemo/modules/product/test/ProductManagement.Application.Tests/ProductManagement/ProductAppService_Tests.cs @@ -1,10 +1,9 @@ using System; -using System.Collections.Generic; using System.Linq; -using System.Text; using System.Threading.Tasks; using Shouldly; using Volo.Abp.Application.Dtos; +using Volo.Abp.Domain.Repositories; using Xunit; namespace ProductManagement @@ -12,13 +11,13 @@ namespace ProductManagement public class ProductAppService_Tests : ProductManagementApplicationTestBase { private readonly IProductAppService _productAppService; - private readonly IProductRepository _productRepository; + private readonly IRepository _productRepository; private readonly ProductManagementTestData _testData; public ProductAppService_Tests() { _productAppService = GetRequiredService(); - _productRepository = GetRequiredService(); + _productRepository = GetRequiredService>(); _testData = GetRequiredService(); }