微服务解决方案:添加新的微服务
你必须拥有ABP商业版或更高级别的许可证才能创建微服务解决方案。
本文档解释了如何向微服务解决方案模板添加新的微服务。在解决方案模板中,根目录下有一个名为services的文件夹,其中包含解决方案中的所有微服务。每个微服务都是一个独立的ASP.NET Core应用程序,可以独立开发、测试和部署。
此外,在根目录中还有一个名为_templates的文件夹。此文件夹包含可用于创建新微服务、API网关和应用程序的模板。这些模板可以根据你的需求进行定制。
添加新的微服务
要向解决方案添加新的微服务,你可以使用service_nolayers模板。此模板会创建一个带有必要配置和依赖项的新ASP.NET Core应用程序。按照以下步骤添加新的微服务:
在ABP Studio的解决方案资源管理器中,右键单击services文件夹,选择添加 -> 新模块 -> 微服务。
你也可以在项目创建过程中通过使用"附加服务"屏幕添加微服务。更多详细信息,请参阅附加服务部分。
这将打开创建新模块对话框。输入新微服务的名称,如果需要则指定输出目录,然后单击下一步按钮。有一个命名约定:模块名称应包含解决方案名称作为前缀,并且模块名称中不允许使用点(.)字符。
选择数据库提供程序,然后单击创建按钮。
当你创建一个新的微服务时,可以选择启用与当前解决方案的集成。如果勾选启用集成,新的微服务将被添加到解决方案中,并且必要的配置会自动完成,因此无需手动配置。如果不勾选启用集成选项,你将需要手动配置新的微服务。你可以按照本文档中的步骤操作,从配置 appsettings.json 部分开始。
新的微服务已创建并添加到解决方案中。你可以在services文件夹中看到新的微服务。
配置 appsettings.json
新的微服务已创建了必要的配置和依赖项。我们应该通过修改appsettings.json文件来配置几个部分:
- 设置
CorsOrigins以允许Web网关访问该微服务。 - 设置
AuthServer配置以使微服务能够对用户进行身份验证和授权。
你可以复制现有微服务的配置,并根据新的微服务进行修改。以下是ProductService微服务的appsettings.json文件示例。
{
"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",
"ProductService": "Server=localhost,1434; User Id=sa; Password=myPassw@rd; Database=Bookstore_ProductService; TrustServerCertificate=true"
},
"App": {
- "CorsOrigins": "http://localhost:webgateway_port",
+ "CorsOrigins": "http://localhost:44333",
"EnablePII": false
},
"Swagger": {
"IsEnabled": true
},
"AuthServer": {
- "Authority": "http://localhost:authserver_port",
- "MetaAddress": "http://localhost:authserver_port",
+ "Authority": "http://localhost:44387",
+ "MetaAddress": "http://localhost:44387",
"RequireHttpsMetadata": "false",
"SwaggerClientId": "SwaggerTestUI",
"Audience": "ProductService"
},
"Redis": {
"Configuration": "localhost:6379"
},
"RabbitMQ": {
"Connections": {
"Default": {
"HostName": "localhost"
}
},
"EventBus": {
"ClientName": "Bookstore_ProductService",
"ExchangeName": "Bookstore"
}
},
"AbpDistributedCache": {
"KeyPrefix": "Bookstore:"
},
"DataProtection": {
"ApplicationName": "Bookstore",
"Keys": "Bookstore-Protection-Keys"
},
"ElasticSearch": {
"IsLoggingEnabled": "true",
"Url": "http://localhost:9200"
},
"StringEncryption": {
"DefaultPassPhrase": "PDAWjbshpwlOwNB6"
}
}
配置 OpenId 选项
我们应该通过修改Identity服务中的OpenIddictDataSeeder来配置OpenId选项。以下是Product微服务的OpenIddictDataSeeder选项示例。
在OpenIddictDataSeeder类的CreateApiScopesAsync和CreateSwaggerClientsAsync方法中创建API范围,并为Swagger客户端添加所需的API范围。
private async Task CreateApiScopesAsync()
{
await CreateScopesAsync("AuthServer");
await CreateScopesAsync("IdentityService");
await CreateScopesAsync("AdministrationService");
+ await CreateScopesAsync("ProductService");
}
private async Task CreateSwaggerClientsAsync()
{
await CreateSwaggerClientAsync("SwaggerTestUI", new[]
{
"AuthServer",
"IdentityService",
"AdministrationService",
+ "ProductService"
});
}
在CreateSwaggerClientAsync方法中添加新服务的重定向URL。
private async Task CreateSwaggerClientAsync(string clientId, string[] scopes)
{
...
...
...
var administrationServiceRootUrl = _configuration["OpenIddict:Resources:AdministrationService:RootUrl"]!.TrimEnd('/');
+ var productServiceRootUrl = _configuration["OpenIddict:Resources:ProductService:RootUrl"]!.TrimEnd('/');
await CreateOrUpdateApplicationAsync(
name: clientId,
type: OpenIddictConstants.ClientTypes.Public,
consentType: OpenIddictConstants.ConsentTypes.Implicit,
displayName: "Swagger 测试客户端",
secret: null,
grantTypes: new List<string>
{
OpenIddictConstants.GrantTypes.AuthorizationCode,
},
scopes: commonScopes.Union(scopes).ToList(),
redirectUris: new List<string> {
$"{webGatewaySwaggerRootUrl}/swagger/oauth2-redirect.html",
$"{authServerRootUrl}/swagger/oauth2-redirect.html",
$"{identityServiceRootUrl}/swagger/oauth2-redirect.html",
$"{administrationServiceRootUrl}/swagger/oauth2-redirect.html",
+ $"{productServiceRootUrl}/swagger/oauth2-redirect.html"
}
);
}
在CreateClientsAsync方法中为Web(前端)应用程序添加允许的范围。你可能为不同的UI应用程序(如Web、Angular、React等)拥有不同的客户端。确保将这些新的微服务添加到这些客户端的允许范围中。
private async Task CreateClientsAsync()
{
var commonScopes = new List<string>
{
OpenIddictConstants.Permissions.Scopes.Address,
OpenIddictConstants.Permissions.Scopes.Email,
OpenIddictConstants.Permissions.Scopes.Phone,
OpenIddictConstants.Permissions.Scopes.Profile,
OpenIddictConstants.Permissions.Scopes.Roles
};
//Web 客户端
var webClientRootUrl = _configuration["OpenIddict:Applications:Web:RootUrl"]!.EnsureEndsWith('/');
await CreateOrUpdateApplicationAsync(
name: "Web",
type: OpenIddictConstants.ClientTypes.Confidential,
consentType: OpenIddictConstants.ConsentTypes.Implicit,
displayName: "Web 客户端",
secret: "1q2w3e*",
grantTypes: new List<string>
{
OpenIddictConstants.GrantTypes.AuthorizationCode,
OpenIddictConstants.GrantTypes.Implicit
},
scopes: commonScopes.Union(new[]
{
"AuthServer",
"IdentityService",
"SaasService",
"AuditLoggingService",
"AdministrationService",
+ "ProductService"
}).ToList(),
redirectUris: new List<string> { $"{webClientRootUrl}signin-oidc" },
postLogoutRedirectUris: new List<string>() { $"{webClientRootUrl}signout-callback-oidc" },
clientUri: webClientRootUrl,
logoUri: "/images/clients/aspnetcore.svg"
);
}
将新服务的URL添加到Identity微服务的appsettings.json文件中。在此示例中,我们将编辑 Acme.Bookstore.IdentityService 项目的 appsettings.json 文件。
"OpenIddict": {
"Applications": {
...
},
"Resources": {
...
+ "ProductService": {
+ "RootUrl": "http://localhost:44350"
+ }
}
}
配置 AuthServer
我们应该为 AuthServer 应用程序的 appsettings.json 文件配置 CorsOrigins 和 RedirectAllowedUrls 部分。
...
"App": {
"SelfUrl": "http://localhost:***",
- "CorsOrigins": "http://localhost:44358,..",
+ "CorsOrigins": "http://localhost:44358,..,http://localhost:44350",
"EnablePII": false,
- "RedirectAllowedUrls": "http://localhost:44358,..",
+ "RedirectAllowedUrls": "http://localhost:44358,..,http://localhost:44350"
},
...
配置 API 网关
我们应该配置API网关以访问新的微服务。首先,将 Product 部分添加到 WebGateway 项目的 appsettings.json 文件中。在此示例中,我们将编辑 Acme.Bookstore.WebGateway 项目的 appsettings.json 文件。
"ReverseProxy": {
"Routes": {
...
+ "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": {
...
+ "Product": {
+ "Destinations": {
+ "Product": {
+ "Address": "http://localhost:44350/"
+ }
}
}
}
}
之后,打开 WebGateway 项目中的 ProjectNameWebGatewayModule 类,并在 ConfigureSwaggerUI 方法中添加 ProductService。在此示例中,我们将编辑 BookstoreWebGatewayModule 文件。
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"
);
}
配置 UI 服务
我们应该配置UI应用程序,以允许新的微服务通过Web网关访问。为此,我们应该在 Web 或 Blazor 应用程序的 ProjectName...Module 类的 ConfigureAuthentication 方法中添加新的微服务范围。在此示例中,我们将编辑 BookstoreWebModule 文件。
private void ConfigureAuthentication(ServiceConfigurationContext context, IConfiguration configuration)
{
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");
});
...
}
类似地,如果你有一个Angular应用程序,你应该在 environment.ts 的 oAuthConfig 中添加新的服务范围:
const baseUrl = 'http://localhost:4200';
const oAuthConfig = {
issuer: 'http://localhost:44387',
redirectUri: baseUrl,
clientId: 'Angular',
responseType: 'code',
- scope: 'openid profile email roles AuthServer IdentityService AdministrationService',
+ scope: 'openid profile email roles AuthServer IdentityService AdministrationService ProductService',
requireHttps: false
};
Prometheus 的 Docker 配置
如果你想在调试解决方案时使用Prometheus监控新的微服务,你应该将新的微服务添加到 etc/docker/prometheus 文件夹中的 prometheus.yml 文件中。你可以复制现有微服务的配置,并根据新的微服务进行修改。以下是 Product 微服务的 prometheus.yml 文件示例。
- job_name: 'authserver'
scheme: http
metrics_path: 'metrics'
static_configs:
- targets: ['host.docker.internal:44398']
...
+ - job_name: 'product'
+ scheme: http
+ metrics_path: 'metrics'
+ static_configs:
+ - targets: ['host.docker.internal:44350']
为新微服务创建 Helm 图表
如果你希望将新的微服务部署到Kubernetes,你应该为该新的微服务创建一个Helm图表。
首先,我们需要将新的微服务添加到 etc/helm 文件夹中的 build-all-images.ps1 脚本中。你可以复制现有微服务的配置,并根据新的微服务进行修改。以下是 Product 微服务的 build-all-images.ps1 脚本示例。
...
./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
然后,我们需要将连接字符串添加到 etc/helm/projectname 文件夹中的 values.projectname-local.yaml 文件中。以下是 Product 微服务的 values.bookstore-local.yaml 文件示例。
global:
...
connectionStrings:
...
+ product: "Server=[RELEASE_NAME]-sqlserver,1433; Database=Bookstore_ProductService; User Id=sa; Password=myPassw@rd; TrustServerCertificate=True"
之后,我们需要为新的微服务创建一个新的Helm图表。你可以复制现有微服务的配置,并根据新的微服务进行修改。以下是 Product 微服务的 product Helm图表示例。
产品微服务的 values.yaml 文件。
image:
repository: "bookstore/product"
tag: "latest"
pullPolicy: IfNotPresent
swagger:
isEnabled: "true"
产品微服务的 Chart.yaml 文件。
apiVersion: v2
name: product
version: 1.0.0
appVersion: "1.0"
description: Bookstore 产品服务
产品微服务的 product.yaml 文件。
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: "DOTNET_ENVIRONMENT"
value: "{{ .Values.global.dotnetEnvironment }}"
- name: "ConnectionStrings__Administration"
value: "{{ .Values.global.connectionStrings.administration | replace "[RELEASE_NAME]" .Release.Name }}"
- name: "ConnectionStrings__AbpBlobStoring"
value: "{{ .Values.global.connectionStrings.blobStoring | replace "[RELEASE_NAME]" .Release.Name }}"
- name: "ConnectionStrings__ProductService"
value: "{{ .Values.global.connectionStrings.product | replace "[RELEASE_NAME]" .Release.Name }}"
...
产品微服务的 product-service.yaml 文件。
apiVersion: v1
kind: Service
metadata:
labels:
name: "{{ .Release.Name }}-{{ .Chart.Name }}"
name: "{{ .Release.Name }}-{{ .Chart.Name }}"
spec:
ports:
- name: "80"
port: 80
selector:
app: "{{ .Release.Name }}-{{ .Chart.Name }}"
创建Helm图表后,你可以在ABP Studio中刷新子图表。
然后,更新元数据信息,右键单击微服务名称子图表,选择属性,这将打开图表属性窗口。你应在元数据选项卡中添加以下键。
projectPath:指向微服务主机应用程序项目的路径。在bookstore示例中,此值为../../services/product/Acme.Bookstore.ProductService/Acme.Bookstore.ProductService.csproj。imageName:当我们构建Docker镜像时,它使用此值作为Docker镜像名称。我们将在Helm图表值中使用它。projectType:你可以为Angular和.NET项目添加Helm图表,这就是为什么我们应该明确指定项目类型。
在图表属性 -> Kubernetes服务选项卡中添加Kubernetes服务。
此值应与解决方案运行程序应用程序的 Kubernetes服务 值相同。这是浏览所必需的,因为当我们连接到Kubernetes集群时,我们应该浏览Kubernetes服务而不是使用启动URL。
最后但同样重要的是,我们需要为身份、认证服务器和网关应用程序配置Helm图表环境变量。
以下是 Identity 微服务的 identity.yaml 文件示例。
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"
以下是 AuthServer 应用程序的 authserver.yaml 文件示例。
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"
以下是 WebApiGateway 应用程序的 webapigateway.yaml 文件示例。
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"
定制微服务模板
如果需要,你可以定制微服务模板。通过打开根目录中的 _templates 文件夹,然后打开 service_nolayers 文件夹,向模板添加新配置、依赖项或模块。根据需要修改 service_nolayers 模板。命名约定规定,microservicename 代表创建时微服务的名称。在模板文件中使用 microservicename 进行动态命名。现有的 service_nolayers 模板默认不包含SaaS和审计日志模块。如果要创建包含这些模块的解决方案,请将必要的配置添加到 service_nolayers 模板文件中。
为新微服务开发 UI
将新的微服务添加到解决方案后,你可以为该新的微服务开发UI。对于.NET应用程序,你可以将微服务的Contracts包添加到UI应用程序,以访问新微服务提供的服务。之后,你可以使用generate-proxy命令为新微服务生成代理类。
abp generate-proxy -t csharp -url http://localhost:44333/ -m product --without-contracts
接下来,开始在UI应用程序中为新的微服务创建页面和组件。类似地,如果你有Angular应用程序,你可以使用generate-proxy命令为新微服务生成代理类并开始开发UI。
abp generate-proxy -t ng -url http://localhost:44333/ -m product
抠丁客











