采用 DDD(领域驱动设计)思想搭建多租户 RBAC 系统,需要明确区分 接口层(Interface)应用层(Application)领域层(Domain)基础设施层(Infrastructure)。下面给出一个符合 Go 语言惯例的项目结构方案,核心模型包括租户、公司、部门、用户、角色、菜单(权限),并体现多租户隔离。

一、顶层目录结构

project/
├── cmd/
│   └── server/
│       └── main.go                 # 程序入口,依赖组装
├── internal/
│   ├── interface/                  # 接口层(用户界面)
│   │   ├── web/                    # HTTP 相关(gin)
│   │   │   ├── handler/            # 请求处理器
│   │   │   ├── middleware/         # 认证、租户、权限中间件
│   │   │   ├── dto/                # 数据传输对象(请求/响应)
│   │   │   └── router/             # 路由注册
│   │   └── rpc/                    # 其他协议(gRPC等),暂略
│   ├── application/                # 应用层(用例编排)
│   │   ├── user/                   # 用户应用服务
│   │   ├── role/                   # 角色应用服务
│   │   ├── menu/                   # 菜单应用服务
│   │   ├── tenant/                 # 租户应用服务
│   │   └── common/                 # 通用应用服务(如认证、权限校验)
│   ├── domain/                     # 领域层(核心业务逻辑)
│   │   ├── tenant/                 # 租户聚合
│   │   ├── company/                # 公司聚合(或实体)
│   │   ├── department/             # 部门聚合(或实体)
│   │   ├── user/                   # 用户聚合
│   │   ├── role/                   # 角色聚合
│   │   ├── menu/                   # 菜单聚合(权限)
│   │   ├── common/                 # 共享领域(值对象、领域事件、仓储接口)
│   │   └── repository/             # 仓储接口定义(由基础设施实现)
│   └── infrastructure/             # 基础设施层
│       ├── persistence/            # 持久化(Gorm 实现)
│       │   ├── mysql/              # MySQL 具体实现
│       │   │   ├── tenant_repo.go
│       │   │   ├── user_repo.go
│       │   │   └── ...
│       │   ├── model/              # Gorm 数据模型(与领域对象分离)
│       │   └── mapper/             # 领域对象 <-> 数据模型 转换
│       ├── cache/                  # 缓存(freecache/redis)
│       ├── idgen/                  # ID 生成(雪花算法等)
│       ├── event/                  # 事件总线(可选)
│       └── security/               # 安全(JWT、密码加密)
├── pkg/                            # 可导出的公共库
│   ├── errors/                     # 自定义错误
│   └── utils/                      # 工具函数
├── configs/                        # 配置文件
├── scripts/                        # 构建脚本
└── go.mod

二、各层详细职责与依赖关系

1. 接口层(internal/interface/web

  • 职责:处理 HTTP 请求,参数解析与校验,调用应用层服务,返回响应。
  • 不包含业务逻辑,只做协议转换。
  • 依赖:应用层服务接口(通过依赖注入)。
  • 典型文件
    • handler/user_handler.go:接收 gin.Context,调用 userApplicationService.Create(...)
    • dto/user_dto.go:请求/响应结构体(区别于领域对象)。
    • middleware/auth.gomiddleware/tenant.gomiddleware/rbac.go
    • router/router.go:组装路由和中间件。

2. 应用层(internal/application

  • 职责:编排领域对象和基础设施,实现用例(Use Case)。事务边界通常在此层。
  • 不包含领域规则,规则在领域层。
  • 依赖:领域层的仓储接口、领域服务、其他应用服务。
  • 结构:按聚合根拆分子包(如 userroletenant),每个子包包含:
    • service.go:应用服务(如 UserApplicationService),方法名体现用例(如 CreateUserAssignRoles)。
    • assembler.go:(可选)将 DTO 转换为领域对象或值对象。
  • 示例application/user/service.go 中的 CreateUser 会调用 userRepo.Save(),并可能发布领域事件。

3. 领域层(internal/domain

  • 核心:表达业务概念、规则、逻辑。
  • 包含
    • 聚合根(Aggregate Root):如 UserRoleTenantCompanyDepartmentMenu
    • 实体(Entity):如 UserRole(关联实体)、MenuNode
    • 值对象(Value Object):如 EmailPermissionTenantIDUserID
    • 领域事件(Domain Event):如 UserCreatedEventRolePermissionChangedEvent
    • 仓储接口(Repository Interface):定义在 domain/repository/ 中,如 UserRepositoryRoleRepository
    • 领域服务(Domain Service):处理跨聚合的业务逻辑,如 AuthorizationService(检查权限)、TenantProvisioningService(创建租户时初始化角色/菜单)。
  • 多租户隔离:领域对象中应包含 TenantID 值对象,仓储接口方法强制接收 tenantID
  • 权限模型Menu 聚合包含 Permission 值对象;Role 聚合包含 RoleMenus 实体集合。

4. 基础设施层(internal/infrastructure

  • 职责:实现领域层定义的接口,提供技术支撑。
  • 子目录
    • persistence/mysql/:实现 UserRepositoryRoleRepository 等,使用 Gorm 操作数据库。
    • persistence/model/:Gorm 的持久化模型(与领域对象分离,如 UserPORolePO)。
    • persistence/mapper/:领域对象与 PO 之间的转换。
    • cache/:实现缓存接口(如 RolePermissionCache)。
    • idgen/:实现 ID 生成器。
    • event/:实现事件发布(同步/异步)。
  • 注意:基础设施层应通过依赖注入提供给应用层,不直接依赖上层。

三、聚合划分与边界

基于业务模型,可以划分以下聚合(推荐以聚合根为边界):

聚合根 包含实体/值对象 仓储
Tenant 租户基本信息、状态、套餐配置 TenantRepo
Company 公司名称、法人、地址,下级公司(自关联) CompanyRepo
Department 部门名称、负责人,上级部门(自关联) DeptRepo
User 用户名、密码、归属部门,关联的角色列表(角色ID集合) UserRepo
Role 角色名称、类型(模板/自定义),含菜单权限集合 RoleRepo
Menu 菜单节点、权限标识、父子关系 MenuRepo

其中 CompanyDepartment 在简单场景下可以不是聚合根,而是作为 Tenant 下的实体,但为了职责清晰,仍可单独拆出。注意:UserRole 的关系通常通过 UserRole 关联实体处理,但为简化,可在 User 聚合中持有 []RoleID,或在 Role 聚合中持有 []UserID。建议在 User 聚合中维护角色列表,因为用户是操作主体。

四、关键层间交互示例(仅描述)

  1. HTTP 请求handler 解析参数生成 DTO。
  2. Handler 调用 application/user/service.CreateUser(dto)
  3. Application Service
    • 校验 DTO 基本合法性。
    • 从仓储获取必要的数据(如检查用户名是否重复,部门是否存在)。
    • 创建领域对象 User(通过工厂方法)。
    • 调用 userRepo.Save(user)(基础设施实现)。
    • 提交事务(数据库事务边界在应用层)。
    • 发布 UserCreatedEvent
  4. 领域层User 对象包含业务规则(如用户名不能包含特殊字符、密码必须加密)。
  5. 基础设施层UserRepositoryImplUser 对象转换为 UserPO,使用 Gorm 存入数据库。

五、处理跨聚合操作举例(租户初始化)

当创建新租户时,需要:

  • 创建租户聚合(租户根)。
  • 创建默认公司/部门。
  • 克隆平台模板角色和菜单到该租户下(写操作)。

这些逻辑涉及多个聚合(Tenant、Company、Department、Role、Menu),属于领域服务,可放在 domain/tenant/tenant_provisioning_service.go,由应用层调用。

六、依赖注入与组装

cmd/server/main.go 中:

  • 初始化 Gorm DB、Redis、FreeCache 等。
  • 创建各个仓储实现(基础设施层)。
  • 创建领域服务、应用服务(依赖仓储接口)。
  • 创建 Handler(依赖应用服务)。
  • 将 Handler 注册到 Gin Router。

七、总结

上述结构遵循 DDD 分层,核心业务逻辑封装在 domain,外部依赖(Web、DB、Cache)在 infrastructureinterface。多租户隔离体现在:TenantID 作为值对象贯穿领域层;仓储接口方法均接收 tenantID;应用层和中间件确保当前租户上下文正确传递。

该方案适合中大型项目,保持高内聚低耦合,便于长期演进和测试。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐