diff --git a/docs/en/solution-templates/microservice/adding-new-microservices.md b/docs/en/solution-templates/microservice/adding-new-microservices.md index 2ff864cd0d..30eb4670ec 100644 --- a/docs/en/solution-templates/microservice/adding-new-microservices.md +++ b/docs/en/solution-templates/microservice/adding-new-microservices.md @@ -16,6 +16,8 @@ This document explains how to add new microservices to the microservice solution Additionally, there is a folder named `_templates` in the root directory. This folder contains templates you can use to create new microservices, API gateways, and applications. These templates can be customized according to your needs. +![folder-structure](images/folder-structure.png) + ## Adding a New Microservice To add a new microservice to the solution, you can use the `service_nolayers` template. This template creates a new ASP.NET Core application with the necessary configurations and dependencies. Follow the steps below to add a new microservice: @@ -40,29 +42,34 @@ The new microservice is created and added to the solution. You can see the new m The new microservice is created with the necessary configurations and dependencies. We should configure several sections by modifying the `appsettings.json` file: * Set the `Administration` & `AbpBlobStoring` connection strings. - * Set the correct `StringEncryption` key. * Set the `CorsOrigins` to allow the web gateway to access the microservice. * Set the `AuthServer` configurations to enable the microservice to authenticate and authorize users. + * Set the correct `StringEncryption` key. You can copy the configurations from the existing microservices and modify them according to the new microservice. Below is an example of the `appsettings.json` file for the `ProductService` microservice. -```json +```diff { "ConnectionStrings": { - "Administration": "Server=localhost,1434; User Id=sa; Password=myPassw@rd; Database=Bookstore_Administration; TrustServerCertificate=true", - "AbpBlobStoring": "Server=localhost,1434; User Id=sa; Password=myPassw@rd; Database=Bookstore_BlobStoring; TrustServerCertificate=true", +- "Administration": "Administration_Database", +- "AbpBlobStoring": "BlobStoring_Database", ++ "Administration": "Server=localhost,1434; User Id=sa; Password=myPassw@rd; Database=Bookstore_Administration; TrustServerCertificate=true", ++ "AbpBlobStoring": "Server=localhost,1434; User Id=sa; Password=myPassw@rd; Database=Bookstore_BlobStoring; TrustServerCertificate=true", "ProductService": "Server=localhost,1434; User Id=sa; Password=myPassw@rd; Database=Bookstore_ProductService; TrustServerCertificate=true" }, "App": { - "CorsOrigins": "http://localhost:44333", +- "CorsOrigins": "http://localhost:webgateway_port", ++ "CorsOrigins": "http://localhost:44333", "EnablePII": false }, "Swagger": { "IsEnabled": true }, "AuthServer": { - "Authority": "http://localhost:44387", - "MetaAddress": "http://localhost:44387", +- "Authority": "http://localhost:authserver_port", +- "MetaAddress": "http://localhost:authserver_port", ++ "Authority": "http://localhost:44387", ++ "MetaAddress": "http://localhost:44387", "RequireHttpsMetadata": "false", "SwaggerClientId": "SwaggerTestUI", "Audience": "ProductService" @@ -93,7 +100,8 @@ You can copy the configurations from the existing microservices and modify them "Url": "http://localhost:9200" }, "StringEncryption": { - "DefaultPassPhrase": "PDAWjbshpwlOwNB6" +- "DefaultPassPhrase": "string_encrryption_key" ++ "DefaultPassPhrase": "PDAWjbshpwlOwNB6" } } ``` @@ -104,13 +112,13 @@ We should configure the OpenId options by modifying the `OpenIddictDataSeeder` i Create API scopes and add the required API scope for Swagger clients in the `CreateApiScopesAsync` and `CreateSwaggerClientsAsync` methods in the `OpenIddictDataSeeder` class. -```csharp +```diff private async Task CreateApiScopesAsync() { await CreateScopesAsync("AuthServer"); await CreateScopesAsync("IdentityService"); await CreateScopesAsync("AdministrationService"); - await CreateScopesAsync("ProductService"); // new service ++ await CreateScopesAsync("ProductService"); } private async Task CreateSwaggerClientsAsync() @@ -120,21 +128,21 @@ private async Task CreateSwaggerClientsAsync() "AuthServer", "IdentityService", "AdministrationService", - "ProductService" // new service ++ "ProductService" }); } ``` Add the redirect URL for the new service in the `CreateSwaggerClientAsync` method. -```csharp +```diff private async Task CreateSwaggerClientAsync(string clientId, string[] scopes) { ... ... ... var administrationServiceRootUrl = _configuration["OpenIddict:Resources:AdministrationService:RootUrl"]!.TrimEnd('/'); - var productServiceServiceRootUrl = _configuration["OpenIddict:Resources:ProductService:RootUrl"]!.TrimEnd('/'); // new service ++ var productServiceServiceRootUrl = _configuration["OpenIddict:Resources:ProductService:RootUrl"]!.TrimEnd('/'); await CreateOrUpdateApplicationAsync( name: clientId, @@ -152,7 +160,7 @@ private async Task CreateSwaggerClientAsync(string clientId, string[] scopes) $"{authServerRootUrl}/swagger/oauth2-redirect.html", $"{identityServiceRootUrl}/swagger/oauth2-redirect.html", $"{administrationServiceRootUrl}/swagger/oauth2-redirect.html", - $"{productServiceServiceRootUrl}/swagger/oauth2-redirect.html", // new service ++ $"{productServiceServiceRootUrl}/swagger/oauth2-redirect.html" } ); } @@ -160,7 +168,7 @@ private async Task CreateSwaggerClientAsync(string clientId, string[] scopes) Add the allowed scope for the web (front-end) application(s) in the `CreateClientsAsync` method. You might have different clients for different UI applications such as web, Angular, React, etc. Ensure you add the new service to the allowed scopes of these clients. -```csharp +```diff private async Task CreateClientsAsync() { var commonScopes = new List @@ -192,7 +200,7 @@ private async Task CreateClientsAsync() "SaasService", "AuditLoggingService", "AdministrationService", - "ProductService" // new service ++ "ProductService" }).ToList(), redirectUris: new List { $"{webClientRootUrl}signin-oidc" }, postLogoutRedirectUris: new List() { $"{webClientRootUrl}signout-callback-oidc" }, @@ -202,110 +210,130 @@ private async Task CreateClientsAsync() } ``` -Add the new service URL to the `appsettings.json` file in the `Identity` service. +Add the new service URL to the `appsettings.json` file in the `Identity` microservice. In this example we're gonna edit the *Acme.Bookstore.IdentityService* project `appsettings.json` file. -```json +```diff "OpenIddict": { "Applications": { ... }, "Resources": { ... - "ProductService": { - "RootUrl": "http://localhost:44350" - } ++ "ProductService": { ++ "RootUrl": "http://localhost:44350" ++ } } } ``` ### Configuring the AuthServer -We should configure the AuthServer for **CORS** and **RedirectAllowedUrls**. +We should configure the *AuthServer* `appsettings.json` file for the **CorsOrigins** and **RedirectAllowedUrls** sections. -```json +```diff +... "App": { "SelfUrl": "http://localhost:***", - "CorsOrigins": "...... ,http://localhost:44350", +- "CorsOrigins": "http://localhost:44358,..", ++ "CorsOrigins": "http://localhost:44358,..,http://localhost:44350", "EnablePII": false, - "RedirectAllowedUrls": "...... ,http://localhost:44350" -} +- "RedirectAllowedUrls": "http://localhost:44358,..", ++ "RedirectAllowedUrls": "http://localhost:44358,..,http://localhost:44350" +}, +... ``` ### Configuring the API Gateway -We should configure the API Gateway to allow the web gateway to access the new microservice. First, we should add the **ProductService** sections to the `appsettings.json` file in the `WebGateway` project. +We should configure the API Gateway to access the new microservice. First, add the **ProductService** sections to the `appsettings.json` file in the `WebGateway` project. In this example we're gonna edit the *Acme.Bookstore.WebGateway* project `appsettings.json` file. -```json +```diff "ReverseProxy": { "Routes": { ... - "ProductService": { - "ClusterId": "ProductService", - "Match": { - "Path": "/api/productservice/{**catch-all}" - } - }, - "ProductServiceSwagger": { - "ClusterId": "ProductService", - "Match": { - "Path": "/swagger-json/ProductService/swagger/v1/swagger.json" - }, - "Transforms": [ - { "PathRemovePrefix": "/swagger-json/ProductService" } - ] - } ++ "ProductService": { ++ "ClusterId": "ProductService", ++ "Match": { ++ "Path": "/api/productservice/{**catch-all}" ++ } ++ }, ++ "ProductServiceSwagger": { ++ "ClusterId": "ProductService", ++ "Match": { ++ "Path": "/swagger-json/ProductService/swagger/v1/swagger.json" ++ }, ++ "Transforms": [ ++ { "PathRemovePrefix": "/swagger-json/ProductService" } ++ ] ++ } }, "Clusters": { ... - "ProductService": { - "Destinations": { - "ProductService": { - "Address": "http://localhost:44350/" - } - } - } ++ "ProductService": { ++ "Destinations": { ++ "ProductService": { ++ "Address": "http://localhost:44350/" ++ } ++ } ++ } } } ``` -Afterwards, open the `ProjectNameWebGatewayModule` class in the `WebGateway` and add the `ProductService` to the `ConfigureSwaggerUI` method. +Afterwards, open the `ProjectNameWebGatewayModule` class in the `WebGateway` project and add the `ProductService` to the `ConfigureSwaggerUI` method. In this example we're gonna edit the `BookstoreWebGatewayModule` file. -```csharp -options.OAuthScopes( - "AdministrationService", - "AuthServer", - "IdentityService", - "ProductService" // new service -); +```diff +private static void ConfigureSwaggerUI( + IProxyConfig proxyConfig, + SwaggerUIOptions options, + IConfiguration configuration) +{ + foreach (var cluster in proxyConfig.Clusters) + { + options.SwaggerEndpoint($"/swagger-json/{cluster.ClusterId}/swagger/v1/swagger.json", $"{cluster.ClusterId} API"); + } + + options.OAuthClientId(configuration["AuthServer:SwaggerClientId"]); + options.OAuthScopes( + "AdministrationService", + "AuthServer", + ..., ++ "ProductService" + ); +} ``` ### Configuring the UI Services -We should configure the UI application(s) to allow the new microservice to access through the web gateway. To do this, we should add the new service scope to the `ConfigureAuthentication` method in the `ProjectName...Module` class in the `Web` or `Blazor` application. +We should configure the UI application(s) to allow the new microservice to access through the web gateway. To do this, we should add the new service scope to the `ConfigureAuthentication` method in the `ProjectName...Module` class in the `Web` or `Blazor` application. In this example we're gonna edit the *BookstoreWebModule* file. -```csharp -context.Services.AddAuthentication(options => -{ - options.DefaultScheme = "Cookies"; - options.DefaultChallengeScheme = "oidc"; -}) -.AddCookie("Cookies", options => +```diff +private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration) { - options.ExpireTimeSpan = TimeSpan.FromDays(365); -}) -.AddAbpOpenIdConnect("oidc", options => -{ - ... - options.Scope.Add("AuthServer"); - options.Scope.Add("IdentityService"); - options.Scope.Add("AdministrationService"); - options.Scope.Add("ProductService"); // new servie -}); + context.Services.AddAuthentication(options => + { + options.DefaultScheme = "Cookies"; + options.DefaultChallengeScheme = "oidc"; + }) + .AddCookie("Cookies", options => + { + options.ExpireTimeSpan = TimeSpan.FromDays(365); + }) + .AddAbpOpenIdConnect("oidc", options => + { + ... + options.Scope.Add("AuthServer"); + options.Scope.Add("IdentityService"); + options.Scope.Add("AdministrationService"); ++ options.Scope.Add("ProductService"); + }); + ... +} ``` Similarly, if you have an Angular application, you should add the new service scope to the `oAuthConfig` in `environment.ts`: -```typescript +```diff const baseUrl = 'http://localhost:4200'; const oAuthConfig = { @@ -313,7 +341,8 @@ const oAuthConfig = { redirectUri: baseUrl, clientId: 'Angular', responseType: 'code', - scope: 'openid profile email roles AuthServer IdentityService AdministrationService ProductService', // new service +- scope: 'openid profile email roles AuthServer IdentityService AdministrationService', ++ scope: 'openid profile email roles AuthServer IdentityService AdministrationService ProductService', requireHttps: false }; ``` @@ -328,12 +357,18 @@ We should add the new microservice to the solution runner [profile](../../studio If you want to monitor the new microservice with Prometheus when you debug the solution, you should add the new microservice to the `prometheus.yml` file in the `etc/docker/prometheus` folder. You can copy the configurations from the existing microservices and modify them according to the new microservice. Below is an example of the `prometheus.yml` file for the `ProductService` microservice. -```yml - - job_name: 'product' +```diff + - job_name: 'authserver' scheme: http metrics_path: 'metrics' static_configs: - - targets: ['host.docker.internal:44350'] + - targets: ['host.docker.internal:44398'] + ... ++ - job_name: 'product' ++ scheme: http ++ metrics_path: 'metrics' ++ static_configs: ++ - targets: ['host.docker.internal:44350'] ``` ## Creating Helm Chart for the New Microservice @@ -342,40 +377,53 @@ If you want to deploy the new microservice to Kubernetes, you should create a He First, we need to add the new microservice to the `build-all-images.ps1` script in the `etc/helm` folder. You can copy the configurations from the existing microservices and modify them according to the new microservice. Below is an example of the `build-all-images.ps1` script for the `ProductService` microservice. -```powershell -./build-image.ps1 -ProjectPath "../../services/product/Acme.Bookstore.ProductService/Acme.Bookstore.ProductService.csproj" -ImageName bookstore/productservice +```diff +... + ./build-image.ps1 -ProjectPath "../../apps/auth-server/Acme.Bookstore.AuthServer/Acme.Bookstore.AuthServer.csproj" -ImageName bookstore/authserver ++ ./build-image.ps1 -ProjectPath "../../services/product/Acme.Bookstore.ProductService/Acme.Bookstore.ProductService.csproj" -ImageName bookstore/productservice ``` Then, we need to add the connection string to the `values.projectname-local.yaml` file in the `etc/helm/projectname` folder. Below is an example of the `values.bookstore-local.yaml` file for the `ProductService` microservice. -```yaml +```diff global: ... connectionStrings: ... - productService: "Server=[RELEASE_NAME]-sqlserver,1433; Database=Bookstore_ProductService; User Id=sa; Password=myPassw@rd; TrustServerCertificate=True" ++ productService: "Server=[RELEASE_NAME]-sqlserver,1433; Database=Bookstore_ProductService; User Id=sa; Password=myPassw@rd; TrustServerCertificate=True" ``` Afterwards, we need to create a new Helm chart for the new microservice. You can copy the configurations from the existing microservices and modify them according to the new microservice. Below is an example of the `productservice` Helm chart for the `ProductService` microservice. +Product microservice `values.yaml` file. + {%{ ```yaml -# values.yaml image: repository: "bookstore/productservice" tag: "latest" pullPolicy: IfNotPresent swagger: isEnabled: "true" +``` +}%} + +Product microservice `Chart.yaml` file. -# Chart.yaml +{%{ +```yaml apiVersion: v2 name: productservice version: 1.0.0 appVersion: "1.0" description: Bookstore Product Service +``` +}%} -# product.yaml +Product microservice `product.yaml` file. + +{%{ +```yaml apiVersion: apps/v1 kind: Deployment metadata: @@ -406,8 +454,13 @@ spec: - name: "ConnectionStrings__ProductService" value: "{{ .Values.global.connectionStrings.productService | replace "[RELEASE_NAME]" .Release.Name }}" ... +``` +}%} + +Product microservice `product-service.yaml` file. -# product-service.yaml +{%{ +```yaml apiVersion: v1 kind: Service metadata: @@ -437,22 +490,97 @@ Add the *Kubernetes Services* in the *Chart Properties* -> *Kubernetes Services* Last but not least, we need to configure the helm chart environments for identity, auth-server, and gateway applications. +Below is an example of the *Identity* microservice `identity.yaml` file. + {%{ -```yaml -# identity.yaml -# Add this line to the "env:" section -- name: "OpenIddict__Resources__ProductService__RootUrl" - value: "http://{{ .Release.Name }}-productservice" - -# authserver.yaml -# Concat the following lines for "App__CorsOrigins" section -- name: "App__CorsOrigins" - value: "...,http://{{ .Release.Name }}-administration,http://{{ .Release.Name }}-productservice" - -# webapigateway.yaml -# Add this line to the "env:" section -- name: "ReverseProxy__Clusters__ProductService__Destinations__ProductService__Address" - value: "http://{{ .Release.Name }}-productservice" +```diff +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ .Release.Name }}-{{ .Chart.Name }}" +spec: + selector: + matchLabels: + app: "{{ .Release.Name }}-{{ .Chart.Name }}" + template: + metadata: + labels: + app: "{{ .Release.Name }}-{{ .Chart.Name }}" + spec: + containers: + - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + name: "{{ .Release.Name }}-{{ .Chart.Name }}" + ports: + - name: "http" + containerPort: 80 + env: + ... ++ - name: "OpenIddict__Resources__ProductService__RootUrl" ++ value: "http://{{ .Release.Name }}-productservice" +``` +}%} + +Below is an example of the *AuthServer* application `authserver.yaml` file. + +{%{ +```diff +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ .Release.Name }}-{{ .Chart.Name }}" +spec: + selector: + matchLabels: + app: "{{ .Release.Name }}-{{ .Chart.Name }}" + template: + metadata: + labels: + app: "{{ .Release.Name }}-{{ .Chart.Name }}" + spec: + containers: + - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + name: "{{ .Release.Name }}-{{ .Chart.Name }}" + ports: + - name: "http" + containerPort: 80 + env: + ... + - name: "App__CorsOrigins" +- value: "...,http://{{ .Release.Name }}-administration" ++ value: "...,http://{{ .Release.Name }}-administration,http://{{ .Release.Name }}-productservice" +``` +}%} + +Below is an example of the *WebApiGateway* application `webapigateway.yaml` file. + +{%{ +```diff +apiVersion: apps/v1 +kind: Deployment +metadata: + name: "{{ .Release.Name }}-{{ .Chart.Name }}" +spec: + selector: + matchLabels: + app: "{{ .Release.Name }}-{{ .Chart.Name }}" + template: + metadata: + labels: + app: "{{ .Release.Name }}-{{ .Chart.Name }}" + spec: + containers: + - image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}" + imagePullPolicy: "{{ .Values.image.pullPolicy }}" + name: "{{ .Release.Name }}-{{ .Chart.Name }}" + ports: + - name: "http" + containerPort: 80 + env: + ... ++ - name: "ReverseProxy__Clusters__ProductService__Destinations__ProductService__Address" ++ value: "http://{{ .Release.Name }}-productservice" ``` }%} diff --git a/docs/en/solution-templates/microservice/images/folder-structure.png b/docs/en/solution-templates/microservice/images/folder-structure.png new file mode 100644 index 0000000000..0342aebdd7 Binary files /dev/null and b/docs/en/solution-templates/microservice/images/folder-structure.png differ