diff --git a/docs/en/get-started/microservice.md b/docs/en/get-started/microservice.md index 9a90be00da..82db8dcc62 100644 --- a/docs/en/get-started/microservice.md +++ b/docs/en/get-started/microservice.md @@ -125,6 +125,8 @@ In the *Solution Runner* section (on the left side) you can see all the runnable > All the leaf items in the *Solution Runner* is called as an *Application* as they are executable applications. +> For a faster start process, first start the *Docker-Dependencies*, then you can start all applications. + As shown in the figure above, the executable applications are grouped into folders like `apps`, `gateways`, `infrastructure`, and `services`. You can start/stop them all, a group (folder) of them, or one by one. Before running the applications, it is good to be sure that all applications are built. To do that, right-click the root item in the *Solution Runner* and select *Build* -> *Build All* action. diff --git a/docs/en/solution-templates/microservice/adding-new-microservices.md b/docs/en/solution-templates/microservice/adding-new-microservices.md index 2ff864cd0d..a1728fe925 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,24 +100,25 @@ 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" } } ``` ### Configuring the OpenId Options -We should configure the OpenId options by modifying the `OpenIddictDataSeeder` in the `Identity` service. Below is an example of the `OpenIddictDataSeeder` options for the `ProductService` microservice. +We should configure the OpenId options by modifying the `OpenIddictDataSeeder` in the `Identity` service. Below is an example of the `OpenIddictDataSeeder` options for the `Product` microservice. 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 productServiceRootUrl = _configuration["OpenIddict:Resources:ProductService:RootUrl"]!.TrimEnd('/'); await CreateOrUpdateApplicationAsync( name: clientId, @@ -152,15 +160,15 @@ 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 ++ $"{productServiceRootUrl}/swagger/oauth2-redirect.html" } ); } ``` -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. +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 microservice 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* application `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 **Product** 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" } - ] - } ++ "Product": { ++ "ClusterId": "Product", ++ "Match": { ++ "Path": "/api/product/{**catch-all}" ++ } ++ }, ++ "ProductSwagger": { ++ "ClusterId": "Product", ++ "Match": { ++ "Path": "/swagger-json/Product/swagger/v1/swagger.json" ++ }, ++ "Transforms": [ ++ { "PathRemovePrefix": "/swagger-json/Product" } ++ ] ++ } }, "Clusters": { ... - "ProductService": { - "Destinations": { - "ProductService": { - "Address": "http://localhost:44350/" - } - } - } ++ "Product": { ++ "Destinations": { ++ "Product": { ++ "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 microservice 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 }; ``` @@ -326,56 +355,75 @@ We should add the new microservice to the solution runner [profile](../../studio ## Docker Configuration for Prometheus -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. +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 `Product` 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 If you want to deploy the new microservice to Kubernetes, you should create a Helm chart for the new microservice. -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. +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 `Product` 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/product ``` -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. +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 `Product` microservice. -```yaml +```diff global: ... connectionStrings: ... - productService: "Server=[RELEASE_NAME]-sqlserver,1433; Database=Bookstore_ProductService; User Id=sa; Password=myPassw@rd; TrustServerCertificate=True" ++ product: "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. +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 `product` Helm chart for the `Product` microservice. + +Product microservice `values.yaml` file. {%{ ```yaml -# values.yaml image: - repository: "bookstore/productservice" + repository: "bookstore/product" tag: "latest" pullPolicy: IfNotPresent swagger: isEnabled: "true" +``` +}%} + +Product microservice `Chart.yaml` file. -# Chart.yaml +{%{ +```yaml apiVersion: v2 -name: productservice +name: product version: 1.0.0 appVersion: "1.0" description: Bookstore Product Service +``` +}%} + +Product microservice `product.yaml` file. -# product.yaml +{%{ +```yaml apiVersion: apps/v1 kind: Deployment metadata: @@ -404,10 +452,15 @@ spec: - name: "ConnectionStrings__AbpBlobStoring" value: "{{ .Values.global.connectionStrings.blobStoring | replace "[RELEASE_NAME]" .Release.Name }}" - name: "ConnectionStrings__ProductService" - value: "{{ .Values.global.connectionStrings.productService | replace "[RELEASE_NAME]" .Release.Name }}" + value: "{{ .Values.global.connectionStrings.product | replace "[RELEASE_NAME]" .Release.Name }}" ... +``` +}%} -# product-service.yaml +Product microservice `product-service.yaml` file. + +{%{ +```yaml apiVersion: v1 kind: Service metadata: @@ -427,32 +480,113 @@ After creating the Helm chart, you can *Refresh Sub Charts* in the ABP Studio. ![kubernetes-refresh-sub-charts](images/kubernetes-refresh-sub-charts.png) -Then, update *Metadata* information right-click the *microservicename* [sub-chart](../../studio/kubernetes.md#subchart), select *Properties* it open *Chart Properties* window. You can edit in the *Metadata* tab. +Then, update *Metadata* information right-click the *microservicename* [sub-chart](../../studio/kubernetes.md#subchart), select *Properties* it open *Chart Properties* window. You should add the following keys in the *Metadata* tab. ![microservice-chart-properties](images/microservice-chart-properties-metadata.png) +- `projectPath`: The path to the microservice host application project. In the bookstore example, this value is `../../services/product/Acme.Bookstore.ProductService/Acme.Bookstore.ProductService.csproj`. +- `imageName`: When we build the Docker image, it uses this value as the Docker image name. We're going to use it in the Helm chart values. +- `projectType`: You can add a Helm chart for Angular and .NET projects, which is why we should explicitly specify the project type. + Add the *Kubernetes Services* in the *Chart Properties* -> *Kubernetes Services* tab. ![microservice-chart-properties-kubernetes-services](images/microservice-chart-properties-kubernetes-services.png) +> This value should be the same as the [solution runner application](./../../studio/running-applications.md#properties) *Kubernetes service* value. It's necessary for browsing because when we connect to the Kubernetes cluster, we should browse the Kubernetes services instead of using the Launch URL. + 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 }}-product" +``` +}%} + +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 }}-product" +``` +}%} + +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__Product__Destinations__Product__Address" ++ value: "http://{{ .Release.Name }}-product" ``` }%} @@ -465,11 +599,11 @@ You can customize the microservice template if needed. Add new configurations, d After adding the new microservice to the solution, you can develop the UI for the new microservice. For .NET applications, you can add the microservice *Contracts* package to the UI application(s) to access the services provided by the new microservice. Afterwards, you can use the [generate-proxy](../../cli/index.md#generate-proxy) command to generate the proxy classes for the new microservice. ```bash -abp generate-proxy -t csharp -url http://localhost:44333/ -m productService --without-contracts +abp generate-proxy -t csharp -url http://localhost:44333/ -m product --without-contracts ``` Next, start creating *Pages* and *Components* for the new microservice in the UI application(s). Similarly, if you have an Angular application, you can use the [generate-proxy](../../cli/index.md#generate-proxy) command to generate the proxy classes for the new microservice and start developing the UI. ```bash -abp generate-proxy -t ng -url http://localhost:44333/ -m productService +abp generate-proxy -t ng -url http://localhost:44333/ -m product ``` \ No newline at end of file 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..926bfaa3d5 Binary files /dev/null and b/docs/en/solution-templates/microservice/images/folder-structure.png differ