第3章 SDK架构设计:分层、模块与适配器

SDK 的架构设计,说到底就是两件事:分清楚责任,和留好扩展口。RBAC 模型嵌进来以后,这两件事会变得特别棘手。为什么?因为权限判断会渗透到 SDK 的每一层——你总不能在每个函数里都写一遍「if 用户有角色 then 放行 else 拒绝」吧?嗯,我见过有人这么干过,结果代码像蜘蛛网,改一个接口要动七八个地方。

这一章我们就把 SDK 的骨架搭起来。我会把 分层设计、模块划分、适配器模式 这三个东西串在一起讲。你在项目中遇到的问题,八成都能在这三个概念里找到解法。

3.1 分层设计:别让权限逻辑到处乱窜

我个人习惯把 SDK 分成四层。你想想看,一个 SDK 最终要给业务方调用,业务方只管「我要查用户的权限」,他根本不应该关心你是从本地缓存取的,还是从远程 API 拉的,更不该关心你的 RBAC 规则引擎怎么运行的。

层名称职责与 RBAC 的关系
接口层(API Layer)对外暴露简洁的 API提供 checkPermission、getRoles、assignRole 等接口
核心业务层(Core Layer)实现 RBAC 模型逻辑角色继承、权限树、会话管理
数据访问层(Data Layer)抽象数据存储方式屏蔽 MySQL、Redis、本地 JSON 文件等差异
适配层(Adapter Layer)对接外部服务与框架让 SDK 能在 Spring、Express、Serverless 等环境中运行
核心原则:每层只解决一个维度的问题。接口层不写 SQL,数据层不抛业务异常,核心层不依赖具体存储。你哪怕以后要把关系数据库换成图数据库,改的也仅仅是数据访问层。

我在项目中遇到过最典型的反例:有人把权限缓存逻辑写在接口层,结果每次换缓存中间件都要改 SDK 的公共 API 签名。说白了,这层混在一起,改一处崩一片。

3.2 模块划分:每个模块只干一件事

分层定好了垂直边界,模块划分定的是水平边界。我建议至少拆成下面 6 个模块:

  1. 认证模块(Auth) —— 身份令牌的签发、验证、刷新。没有身份,谈什么权限?
  2. 角色管理模块(Role) —— 角色 CRUD、角色树、角色继承。RBAC 的核心之一。
  3. 权限管理模块(Permission) —— 资源与操作的权限定义,以及权限与角色的绑定。
  4. 会话模块(Session) —— 用户上下文在请求链中的传递。权限判断需要知道「当前是谁」。
  5. 策略引擎模块(Engine) —— 决策逻辑,比如「是否允许用户 A 对资源 X 执行操作 Y」。
  6. 存储抽象模块(Store) —— 上面提到的数据访问层,对外提供一致的读写接口。
小建议:不要把「日志模块」单独列出来。日志是横切关注点,应该用 AOP 或拦截器统一处理。我在一个项目里试过把日志写进每个模块,最后审计排查时日志散落得到处都是,差点没被运维同事骂死。

3.3 适配器模式:让 SDK 拥抱变化

为什么要提适配器模式?因为 SDK 永远不知道会被扔到什么环境里。我曾经接过一个需求:把我们的 RBAC SDK 嵌入到一个 IoT 网关设备上,那设备跑的是轻量级 Linux,连标准 HTTP 库都没有。如果 SDK 代码里到处都是 hardcode 的 HttpClient 调用,那这个需求就彻底没戏了。

适配器模式在这里的作用是:定义接口,让使用者提供实现。SDK 内部只依赖这些接口,不关心具体实现。

下面是一个简化示例——我们把「持久化存储」抽象成接口:

// 定义适配器接口
public interface PermissionStore {
    List<Role> getRolesByUserId(String userId);
    List<Permission> getPermissionsByRoleId(String roleId);
    void saveRoleAssignment(String userId, String roleId);
}

// SDK 内部只依赖接口
public class RbacEngine {
    private final PermissionStore store;
    
    public boolean checkPermission(String userId, String resource, String action) {
        List<Role> roles = store.getRolesByUserId(userId);
        // ... 业务逻辑
        return true;
    }
}

// 使用者提供具体实现
public class MySqlPermissionStore implements PermissionStore {
    // 实现具体 SQL 查询
}
我曾经踩过的坑:适配器接口设计得太细。比如把 getRolesByUserId 和 getRolesByGroupId 拆成两个独立方法,结果实现者发现两个方法底层 SQL 完全一样,只是参数不同。最后我改成了 unified 查询接口,用 Query 对象传参。别为了过度抽象把适配器搞成「方法膨胀」。

除了存储适配器,你还可能需要:

  • 缓存适配器 —— 让使用者选本地 Map、Caffeine 还是 Redis。
  • 日志适配器 —— 对接使用者的日志框架(SLF4J、log4j、甚至直接 System.out)。
  • 上下文适配器 —— 从 HTTP Header、gRPC Metadata 或消息队列中提取当前用户身份。

3.4 架构总览:一张图串起来

上面说的这些东西,我画成了一张结构图。你一眼就能看清层次和模块关系:

RBAC SDK 四层架构与适配器结构 接口层 (API Layer) checkPermission(uid, resource, action) | getRoles(uid) | assignRole(uid, roleId) 核心业务层 (Core Layer) 策略引擎 | 角色继承 | 权限树解析 | 会话管理 Auth 模块 Role 模块 Permission 模块 Engine 模块 Session 模块 适配层 (Adapter Layer) 存储适配器接口 | 缓存适配器接口 | 日志适配器接口 | 上下文适配器接口 使用者提供实现 —— 适配 MySQL / Redis / SLF4J / HTTP 上下文 数据访问层 (Data Layer) Store 模块 —— 具体读写实现 (MySQL / Redis / JSON / gRPC) 每层只依赖下层接口 适配层隔离变化

你看这张图,从上到下分别是接口层、核心业务层、适配层、数据访问层。中间的核心业务层又拆成了 6 个模块。适配层像个「转换器」,把 SDK 内部接口映射到使用者的基础设施上。

实际落地时的取舍:如果你面向的场景比较单一(比如只服务一套 Java Spring 微服务),可以适当减少适配器接口的数量。我在一个内部工具 SDK 里直接硬编码了 Spring Data JPA,后来发现架构扩展性跟不上,又回头补了适配器。建议:从第一天开始就写好接口抽象,哪怕实现只写一个。

3.5 避坑指南:我记了三年的教训

最后分享一个我当年犯过的错误。那时我刚做 SDK 开发,认为「分层」就是多写几个 package 完事。结果适配器接口放在了核心层里,导致核心业务代码不得不 import 具体的存储实现类。每次改存储层,核心层也要重新编译。后来我花了整整两天重构才把依赖调回来。

规则很简单:核心层只依赖接口定义,接口定义放在一个单独的「契约」包(比如 com.xxx.rbac.contract)里。适配器实现和具体存储实现都在上层或外部模块里。这样你换任何一个实现,核心层一行代码都不用改。