更新于 2026-07-01。本文参考 abpframework/eShopOnAbp 的服务拆分方式,以及 abpframework/abp 当前模板中的宿主组织思路,结合本系列前两篇已经搭好的共享模块,整理出一套更适合当前 ABP 版本的基础服务搭建路线。
如果你希望先理解 ABP 微服务方案的整体骨架,再进入具体业务服务,这一篇可以作为后续章节的基础。
在上一篇文章【ABP框架实践】从零构建微服务解决方案 - 共享项目中,我们已经把共享模块搭好了:本地化、公共宿主配置、网关辅助代码,以及微服务宿主层会复用的基础能力都已经有了落点。接下来就可以开始创建微服务。
这一篇不直接进入 Catalog、Order 这类自定义业务服务,而是先搭三套基础服务:
AdministrationServiceIdentityServiceSaasService
这样安排的原因是,在一套完整的 ABP 微服务架构里,权限、设置、身份、租户这些能力不应散落在各个业务服务里,而应先有明确的服务边界和统一的宿主模型。
读完这一篇,应该可以明确三件事情:
- 理解为什么 ABP 微服务方案通常会先拆出管理、身份、租户三类底座服务。
- 理解一个基础服务的标准分层骨架应该如何组织。
- 理解这些服务在宿主层如何统一接入认证、Swagger、跨域和迁移检查,为后续网关与业务服务打底。
为什么先搭基础服务
在开始做微服务拆分时,一个常见问题是直接从业务模块入手。这样虽然也能运行,但往往会把认证授权、多租户、设置管理、审计日志这些平台能力混进业务服务里,最终造成边界不清、依赖发散。
eShopOnAbp 这个示例项目虽然已经有些年份,但它有一个思路仍然值得借鉴:先把平台级能力拆成独立服务,再让业务服务围绕这些能力组织。这样做的好处是,业务服务可以更专注于领域本身,而身份、权限、租户、配置这些横切能力则有稳定的归属。
从微服务框架的角度看,这三套基础服务分别对应三类底座能力:
- 管理底座:由
AdministrationService承载,用来集中处理权限、设置、功能和审计等后台通用能力。 - 身份底座:由
IdentityService承载,用来集中处理用户、角色、组织单元和身份数据。 - 租户底座:由
SaasService承载,用来集中处理租户及其配置,是多租户系统的核心边界。
先把这三层搭起来,后续再去写网关、认证服务器和业务服务,整体结构会更清晰。
基础服务的统一骨架
参考 eShopOnAbp 的服务组织方式,你会发现这三套基础服务虽然职责不同,但骨架几乎完全一致。一个标准的 ABP 微服务,通常至少会包含下面这些项目层次:
Domain.SharedDomainApplication.ContractsApplicationEntityFrameworkCoreHttpApiHttpApi.Host
这套分层之所以重要,不是因为“官方示例都这么写”,而是因为它正好对应了微服务里几个最容易混淆的职责边界:
Domain.Shared:放整个服务范围内共享的常量、错误码、本地化资源和跨层定义。Domain:放领域模块依赖,以及真正属于领域层的规则和服务。Application.Contracts:向外暴露 DTO、应用服务接口和远程服务契约。Application:实现应用服务、对象映射和应用层编排逻辑。EntityFrameworkCore:承载 DbContext、实体映射和数据库迁移。HttpApi:负责把应用服务暴露成 HTTP API。HttpApi.Host:负责真正启动服务,并接入认证、Swagger、跨域、日志、迁移检查等宿主层能力。
如果把这些边界在一开始就理顺,后续新增业务服务时,通常可以直接复用这套模式,只替换服务自身的领域和模块依赖即可。
推荐的目录组织
前两篇文章里,我们已经在解决方案根目录下准备好了 shared/ 和 etc/。从这一篇开始,建议把每个微服务单独放进 services/ 目录下,再按服务拆分自己的解决方案和 src/ 目录。整体结构可以组织成这样:
1 | services/ |
这种组织方式有几个明显好处:
- 每个服务都能拥有独立的解决方案和项目分层,便于单独构建和演进。
- 共享模块继续留在根目录统一维护,不会和某个具体服务强耦合。
- 当后续再新增
catalog、ordering这类业务服务时,目录层次依旧保持一致。
创建服务解决方案和项目
有了目录组织之后,再来看具体的创建步骤。这里依然以 AdministrationService 为例,但重点不是记住命令本身,而是理解这一套服务骨架的创建方式。
先创建目录和解决方案:
1 | mkdir services/administration |
然后按统一骨架创建七个项目:
1 | dotnet new classlib -n MyCompanyName.MyProjectName.AdministrationService.Domain.Shared -o src/MyCompanyName.MyProjectName.AdministrationService.Domain.Shared --no-restore |
IdentityService 和 SaasService 的创建方式完全一致,只需要替换服务名前缀即可。
创建项目之后,记得做两件事情:
- 把各项目加入服务自己的解决方案。
- 继续引用根目录里的
common.props,保持和前两篇相同的基础构建约定。由于服务项目位于services/<service>/src/<project>/这一层级,项目文件中的相对路径通常要写成../../../../common.props。
这些步骤看起来琐碎,但会直接影响后续整套方案的一致性。越早把项目结构和构建约定统一下来,后面新增服务时越容易保持整洁。
项目引用关系
基础服务的分层不是“摆着好看”,项目引用关系才是真正体现架构边界的地方。三套服务都可以先遵循下面这组稳定关系:
Domain引用Domain.SharedApplication.Contracts引用Domain.SharedApplication引用Application.Contracts和DomainEntityFrameworkCore引用DomainHttpApi引用Application.ContractsHttpApi.Host引用Application、HttpApi、EntityFrameworkCore
除此之外,三个 HttpApi.Host 还应该统一引用前两篇已经准备好的共享模块:
MyCompanyName.MyProjectName.Shared.Hosting.Microservices
这样做的意义在于,宿主层的公共能力只需要在共享模块里维护一次,具体服务只负责声明自己的业务边界,而不必各自重复实现日志、认证、缓存、Swagger 这些横切配置。
AdministrationService:管理底座
AdministrationService 是三套基础服务里最偏平台管理能力的一层。它的边界不在于承接某个具体业务领域,而在于集中承接平台级管理能力,例如:
- 权限管理
- 设置管理
- 功能管理
- 审计日志
- Blob 存储相关后台能力
之所以把这些能力独立成服务,是因为它们通常会被多个业务服务和后台应用共同依赖。如果把它们分散到业务服务里,后续维护成本会明显上升;独立出来之后,它就成为整个平台的管理能力中心。
核心模块依赖
从模块边界上看,AdministrationService 的 Domain 层通常围绕这些 ABP 模块组织:
1 | [ |
这里最重要的不是把所有旧示例里的模块全抄过来,而是理解它的设计意图:这个服务负责承接“平台管理能力”的聚合,因此它的依赖应该围绕管理型模块展开。具体依赖组合,仍应以你当前启用的模块和目标部署拓扑为准。
数据库边界
在持久化层,AdministrationService 的重点是接管各类管理模块对应的 DbContext 接口:
1 | context.Services.AddAbpDbContext<AdministrationServiceDbContext>(options => |
这一步的含义是:权限、设置、功能、审计和 Blob 存储相关的数据,不再分散由各个模块默认持有,而是统一归入 AdministrationService 的数据库边界。
由于本系列前两篇已经以 PostgreSQL 为本地开发数据库基线,因此迁移配置也应保持一致:
1 | Configure<AbpDbContextOptions>(options => |
和上一篇文章里的连接映射对应起来看,AdministrationService 实际承接的是这些模块:
AbpAuditLoggingAbpPermissionManagementAbpSettingManagementAbpFeatureManagementAbpBlobStoring
这就是这个服务的数据库归属边界。
与后续服务的关系
后面的业务服务本身通常不直接实现这些平台级管理能力,而是通过远程调用、共享配置或统一后台入口与 AdministrationService 协作。因此把它先搭好,本质上是在为整个平台的后台能力先立规矩。
IdentityService:身份底座
IdentityService 负责的是整套系统的身份数据,而不是认证服务器本身。它的角色更接近“用户与身份中心”,通常会集中承接这些能力:
- 用户
- 角色
- 组织单元
- 身份相关持久化模型
- 为认证体系提供用户与权限基础数据
把这一层独立出来的价值在于:无论后面有多少业务服务,它们都不应该各自维护用户体系。身份数据本身就属于平台底座,应由统一服务治理。
核心模块依赖
在当前系列的叙事里,IdentityService 的核心领域依赖首先应围绕 AbpIdentity 组织:
1 | [ |
如果你看的是较早的 ABP 示例,可能还会看到 IdentityServer 相关依赖。但在当前 ABP 体系下,更适合按照 OpenIddict 的结构来理解认证体系:IdentityService 负责身份数据,而令牌签发和认证服务器宿主会在后续章节单独展开。
数据库边界
IdentityService 的持久化层重点是接管身份相关上下文,并为认证体系保留数据落点:
1 | context.Services.AddAbpDbContext<IdentityServiceDbContext>(options => |
这里的设计意图很明确:
IIdentityDbContext代表用户、角色等身份数据。IOpenIddictDbContext代表当前认证体系所依赖的持久化数据。
如果你后续决定把认证服务器持久化进一步拆分,也可以再调整边界;但在基础服务阶段,先把身份数据中心立起来,是最稳妥的路线。
和上一篇文章中的连接映射对应起来,IdentityService 至少应该承接:
AbpIdentityOpenIddict
与后续服务的关系
网关、认证服务器、后台应用以及各业务服务,最终都会围绕身份体系工作。因此在整套微服务框架里,IdentityService 更像是一套稳定的身份能力底座,而不是某个可选模块。
SaasService:租户底座
如果系统从一开始就需要支持多租户,那么 SaasService 通常应尽早拆出来。它的职责非常明确:承接租户管理及其相关配置,是整套多租户系统的权威来源。
一旦租户边界明确,后续的网关、认证、业务服务才能在统一租户上下文中运行。否则,不同服务很容易各自维护一套租户识别逻辑,后续协调成本会越来越高。
核心模块依赖
SaasService 的领域层通常会直接围绕租户管理模块组织:
1 | [ |
这个服务的边界非常纯粹:它承接的是平台的多租户能力,而非业务领域建模。
数据库边界
持久化层也同样清晰,核心就是接管租户管理上下文:
1 | context.Services.AddAbpDbContext<SaasServiceDbContext>(options => |
迁移表继续按服务独立命名:
1 | Configure<AbpDbContextOptions>(options => |
这样做的意义在于,租户数据从一开始就有清晰的数据库归属,不会和身份或业务数据混在一起。
与后续服务的关系
后续无论是认证服务器、网关,还是具体业务服务,只要涉及租户识别、租户配置或租户上下文传递,最终都要围绕 SaasService 这一层展开。因此它虽然不直接承接业务功能,却会影响整套系统的运行方式。
宿主层的统一基础能力
三套基础服务真正运行起来,关键不在前面的类库项目,而在 HttpApi.Host。这一层很容易被低估,因为它看起来只是启动程序,实际上却承载了整套服务的运行入口。
在当前 ABP 版本下,更推荐采用 WebApplicationBuilder 这一套现代宿主模型,而不是继续沿用早期示例里常见的 Program + Startup 叙事。原因很简单:当前模板、扩展点和中间件组织方式都已经围绕这一模式稳定下来,继续跟旧写法绑定,只会增加读者的迁移成本。
一个典型的宿主入口可以组织成这样:
1 | var builder = WebApplication.CreateBuilder(args); |
在本系列里,前一篇已经通过共享宿主模块准备好了这套基础能力,因此三个服务的 HttpApi.Host 可以统一围绕“宿主层基础能力”来组织。
模块依赖
三个 HttpApi.Host 模块都应统一依赖:
- 本服务的
HttpApi模块 - 本服务的
Application模块 - 本服务的
EntityFrameworkCore模块 MyProjectNameSharedHostingMicroservicesModule
这意味着,服务自身只需要声明自己的能力边界,而认证、缓存、多租户、日志等基础能力都通过共享宿主模块向下沉淀。
这里还有一个容易忽略的实践点:共享宿主模块并不是把所有基础设施依赖都预先塞进去,而是收敛当前这套方案真正共用的宿主能力。在本文对应的实现里,它主要保留 Redis、分布式锁、多租户、消息总线、JWT Bearer 等通用配置;数据库迁移与具体持久化映射仍然留在各服务自己的 EntityFrameworkCore 与 HttpApi.Host 中。
JWT Bearer
服务间鉴权和 API 访问的第一层入口,是统一的 JWT Bearer 校验。前一篇文章里我们已经准备了 JwtBearerConfigurationHelper,这里可以继续复用:
1 | JwtBearerConfigurationHelper.Configure(context, "AdministrationService"); |
这样每个服务只需要声明自己的 Audience,而 Authority 和 RequireHttpsMetadata 则继续从统一配置读取。对于读者来说,更重要的是理解:这类能力属于宿主层,不属于某个具体业务模块。
CORS
跨域配置也应保持统一,避免每个宿主各写一套不同规则:
1 | context.Services.AddCors(options => |
这类配置本身并不复杂,但它们决定了所有服务在对外暴露 API 时的运行一致性。
Swagger OAuth 集成
对于一套微服务方案来说,Swagger 不只是“为了好看”,它还是你验证 API 契约和认证链路是否正确的重要入口。这里继续复用上一篇文章封装的共享辅助类即可:
1 | SwaggerConfigurationHelper.ConfigureWithAuth( |
从框架搭建视角看,Swagger OAuth 集成属于宿主层基础能力,而不是某个具体应用服务的职责。
数据库迁移检查
还有一个容易被忽略,但在微服务环境里很实用的能力,是启动阶段的数据库迁移检查。
宿主模块里可以统一采用类似下面的模式:
1 | public override void OnPostApplicationInitialization(ApplicationInitializationContext context) |
不同服务只需要替换成各自的迁移检查器即可。这样做的价值在于,服务启动时就能发现数据库状态是否满足运行要求,而不是把问题拖到第一次请求进来之后。