权限继承设计:Modular Monolith DDD角色与用户组实战
引言:权限管理的痛点与DDD解决方案
在复杂业务系统中,权限管理往往面临三大挑战:角色定义混乱、权限分配繁琐、跨模块权限协同困难。尤其在模块化单体(Modular Monolith)架构中,如何在保持模块内聚性的同时实现灵活的权限继承,成为Domain-Driven Design(领域驱动设计)实践中的关键课题。
本文基于真实Modular Monolith项目(modular-monolith-with-ddd),从领域模型设计、数据库架构到查询实现,全面解析角色与权限继承的实战方案。读完本文你将掌握:
- DDD值对象(Value Object)模式在角色定义中的应用
- 基于数据库视图的权限聚合查询实现
- 模块化架构下的权限隔离与跨模块协同策略
- 角色继承与用户组权限的设计模式
一、领域模型设计:角色与权限的DDD表达
1.1 角色定义:值对象模式的应用
在UserAccess模块的领域层中,UserRole类采用值对象(Value Object)模式实现,确保角色定义的不可变性和业务语义的完整性:
// src/Modules/UserAccess/Domain/Users/UserRole.cs
public class UserRole : ValueObject
{
public static UserRole Member => new UserRole(nameof(Member));
public static UserRole Administrator => new UserRole(nameof(Administrator));
public string Value { get; }
private UserRole(string value)
{
this.Value = value;
}
}
设计亮点:
- 通过静态属性定义系统内置角色,避免魔法字符串
- 私有构造函数确保角色实例只能通过预定义常量创建
- 继承
ValueObject基类,重写相等性比较逻辑(由BuildingBlocks提供支持)
1.2 用户-角色关联:聚合根中的权限载体
User聚合根通过_roles字段维护用户与角色的多对多关系,在用户创建时分配初始角色:
// src/Modules/UserAccess/Domain/Users/User.cs
public class User : Entity, IAggregateRoot
{
private List<UserRole> _roles;
private User(
Guid id,
string login,
string password,
string email,
string firstName,
string lastName,
string name,
UserRole role)
{
// 基础属性初始化...
_roles = [role]; // 初始角色分配
this.AddDomainEvent(new UserCreatedDomainEvent(this.Id));
}
public static User CreateAdmin(...) { /* 分配Administrator角色 */ }
public static User CreateUser(...) { /* 分配Member角色 */ }
}
领域事件:用户创建时发布UserCreatedDomainEvent,可用于跨模块权限初始化(如自动分配默认权限集)。
二、数据库架构:权限继承的持久化设计
2.1 权限关联表结构
InitializeDatabase.sql定义了权限系统的核心表结构,采用经典的RBAC(基于角色的访问控制)模型:
-- 用户-角色关联表
CREATE TABLE [users].[UserRoles] (
[UserId] UNIQUEIDENTIFIER NOT NULL,
[RoleCode] NVARCHAR (50) NULL
);
-- 角色-权限关联表
CREATE TABLE [users].[RolesToPermissions] (
[RoleCode] VARCHAR (50) NOT NULL,
[PermissionCode] VARCHAR (50) NOT NULL,
CONSTRAINT [PK_RolesToPermissions_RoleCode_PermissionCode] PRIMARY KEY CLUSTERED ([RoleCode] ASC, [PermissionCode] ASC)
);
-- 权限定义表
CREATE TABLE [users].[Permissions] (
[Code] VARCHAR (50) NOT NULL,
[Name] VARCHAR (100) NOT NULL,
[Description] VARCHAR (255) NULL,
CONSTRAINT [PK_users_Permissions_Code] PRIMARY KEY CLUSTERED ([Code] ASC)
);
2.2 权限继承视图设计
通过视图v_UserPermissions实现角色权限的自动聚合,简化权限查询逻辑:
CREATE VIEW [users].[v_UserPermissions]
AS
SELECT
DISTINCT
[UserRole].UserId,
[RolesToPermission].PermissionCode
FROM [users].UserRoles AS [UserRole]
INNER JOIN [users].RolesToPermissions AS [RolesToPermission]
ON [UserRole].RoleCode = [RolesToPermission].RoleCode
视图作用:
- 隐藏多表关联复杂性,提供用户权限的扁平化查询
- 支持权限继承:当角色权限更新时,用户权限自动生效
- 为跨模块权限查询提供统一数据接口
三、权限查询实现:从数据库到应用服务
3.1 权限查询处理程序
GetUserPermissionsQueryHandler通过查询v_UserPermissions视图获取用户权限列表:
// src/Modules/UserAccess/Application/Authorization/GetUserPermissions/GetUserPermissionsQueryHandler.cs
internal class GetUserPermissionsQueryHandler : IQueryHandler<GetUserPermissionsQuery, List<UserPermissionDto>>
{
private readonly ISqlConnectionFactory _sqlConnectionFactory;
public async Task<List<UserPermissionDto>> Handle(GetUserPermissionsQuery request, CancellationToken cancellationToken)
{
var connection = _sqlConnectionFactory.GetOpenConnection();
const string sql = $"""
SELECT [UserPermission].[PermissionCode] AS [{nameof(UserPermissionDto.Code)}]
FROM [users].[v_UserPermissions] AS [UserPermission]
WHERE [UserPermission].UserId = @UserId
""";
var permissions = await connection.QueryAsync<UserPermissionDto>(sql, new { request.UserId });
return permissions.AsList();
}
}
设计特点:
- 依赖
ISqlConnectionFactory抽象,支持多数据库实现 - 通过Dapper执行原生SQL,直接查询优化后的视图
- 返回DTO对象,实现领域模型与API契约的解耦
3.2 权限继承的隐式实现
虽然代码中未直接定义角色层次结构,但通过以下方式支持权限继承:
- 角色组合:用户可同时拥有多个角色(如Member + Moderator),权限自动合并
- 权限粒度控制:通过细粒度权限定义(如
Meeting.Create、Meeting.Edit)和角色映射实现灵活授权 - 跨模块权限隔离:权限代码可按模块前缀划分(如
Administration.*、Meetings.*)
四、扩展设计:用户组与动态权限管理
4.1 用户组模拟实现方案
尽管当前代码未直接提供用户组(Group)实体,但可通过以下方式模拟用户组功能:
// 扩展User类,添加角色管理方法
public class User : Entity, IAggregateRoot
{
// ...现有代码...
public void AddRole(UserRole role)
{
if (!_roles.Contains(role))
{
_roles.Add(role);
// 发布角色变更事件
this.AddDomainEvent(new UserRoleAddedDomainEvent(this.Id, role));
}
}
}
用户组替代方案:
- 创建"虚拟角色"(如
MarketingTeam),将用户添加到该角色实现组权限 - 在
RolesToPermissions表中为虚拟角色分配特定权限集 - 通过领域服务
UserGroupService管理用户-虚拟角色关联
4.2 权限继承增强建议
基于项目现有架构,可通过以下方式增强权限继承能力:
增强方向:
- 显式角色层次:添加
ParentRoleCode字段到Roles表,实现角色继承 - 条件权限:扩展
RolesToPermissions表,添加Condition字段支持基于上下文的权限判断 - 权限有效期:添加
ValidFrom和ValidTo字段,支持临时权限分配
五、最佳实践与常见陷阱
5.1 DDD权限设计最佳实践
- 权限作为领域概念:核心权限逻辑(如"只有管理员可删除会议")应作为领域规则实现
// 领域规则示例(Meetings模块)
public class Meeting : Entity
{
public void Delete(User deletingUser)
{
if (!deletingUser.HasPermission(PermissionCodes.Meeting.Delete))
{
throw new BusinessRuleValidationException("Insufficient permissions to delete meeting");
}
// 其他删除逻辑...
}
}
- 权限检查集中化:通过装饰器模式统一权限验证
public class PermissionCommandHandlerDecorator<TCommand> : ICommandHandler<TCommand>
{
private readonly ICommandHandler<TCommand> _decorated;
private readonly IPermissionChecker _permissionChecker;
public async Task Handle(TCommand command, CancellationToken cancellationToken)
{
var requiredPermission = GetPermissionForCommand(typeof(TCommand));
if (!await _permissionChecker.HasPermission(requiredPermission))
{
throw new AccessDeniedException();
}
await _decorated.Handle(command, cancellationToken);
}
}
5.2 常见陷阱与解决方案
| 问题 | 解决方案 |
|---|---|
| 权限检查散布在UI层 | 使用命令/查询装饰器统一拦截 |
| 权限粒度粗导致授权不灵活 | 细化权限定义(如Meeting.Create vs Meeting.Edit) |
| 角色爆炸难以维护 | 引入权限组和角色继承 |
| 跨模块权限协同复杂 | 通过集成事件同步权限变更 |
六、总结与扩展方向
本文深入分析了Modular Monolith项目中基于DDD的权限继承设计,核心要点包括:
- 值对象角色定义:确保角色语义明确且不可变
- RBAC数据库模型:通过角色-权限关联实现基础权限继承
- 视图驱动查询:简化权限聚合查询,提升性能
- 领域事件驱动:支持权限变更的跨模块同步
未来扩展方向:
- 实现角色层次结构(如
SuperAdmin继承Admin权限) - 添加用户组实体,支持批量权限管理
- 引入属性基础访问控制(ABAC),基于上下文动态授权
- 开发权限管理UI,支持动态角色-权限配置
通过本文介绍的设计模式和实现方式,你可以在模块化单体架构中构建既灵活又安全的权限系统,为未来向微服务架构演进奠定基础。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



