Browse Source

Merge pull request #20691 from abpframework/auto-merge/rel-8-3/2958

Merge branch dev with rel-8.3
pull/20704/head
maliming 2 years ago
committed by GitHub
parent
commit
23a325dc40
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      docs/en/get-started/microservice.md
  2. 366
      docs/en/solution-templates/microservice/adding-new-microservices.md
  3. BIN
      docs/en/solution-templates/microservice/images/folder-structure.png

2
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.

366
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<string>
@ -192,7 +200,7 @@ private async Task CreateClientsAsync()
"SaasService",
"AuditLoggingService",
"AdministrationService",
"ProductService" // new service
+ "ProductService"
}).ToList(),
redirectUris: new List<string> { $"{webClientRootUrl}signin-oidc" },
postLogoutRedirectUris: new List<string>() { $"{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
```

BIN
docs/en/solution-templates/microservice/images/folder-structure.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Loading…
Cancel
Save