如何将项目中公共模块封装为可复用Skill:从设计模式到工程实践

2次阅读
没有评论

共计 2455 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

重复造轮子的代价

去年参与一个微服务改造项目时,发现六个子系统各自实现了权限校验模块。虽然业务规则相同,但由于实现差异导致:

如何将项目中公共模块封装为可复用 Skill:从设计模式到工程实践

  • 新员工需要阅读 5 种不同风格的鉴权代码
  • 修复越权漏洞时需同步修改 6 个代码库
  • 性能优化方案无法统一实施

这让我意识到: 公共模块的碎片化实现是研发效能的第一杀手

Utils 与 Skill 的本质区别

传统 Utils 类通常存在两大缺陷:

  1. 强耦合 :静态方法直接依赖具体实现

    // 反例:硬编码的加密工具类
    public class CryptoUtils {public static String encrypt(String input) {return AES.encrypt(input); // 无法替换算法
        }
    }

  2. 无契约 :方法签名即全部约定

Skill 化方案通过接口隔离原则改进:

// Skill 标准接口
interface LoggingSkill {
  /**
   * @param level 日志级别
   * @param message 支持结构化数据
   */
  log(level: LogLevel, message: LogEntry): Promise<void>;
}

// 实现端遵守接口契约
class ELKLogger implements LoggingSkill {async log(level: LogLevel, entry: LogEntry) {
    await elkClient.index({index: `logs-${new Date().toISOString().slice(0,10)}`,
      body: {...entry, severity: level}
    });
  }
}

核心实现三部曲

1. 标准化接口设计

遵循三个关键原则:

  • 单一职责 :每个 Skill 只解决一个问题域
  • 显式依赖 :所需资源通过构造函数注入
  • 异常契约 :明确声明可能抛出的错误类型
/**
 * 权限校验 Skill
 * @throws AccessDeniedException 当校验失败时抛出
 */
public interface AuthSkill {void checkPermission(User user, Resource resource) 
        throws AccessDeniedException;
}

2. 依赖注入集成

Spring 集成示例:

@SkillComponent // 自定义注解标识 Skill
public class DatabaseAuthSkill implements AuthSkill {
    private final UserRepository repo;

    @Autowired
    public DatabaseAuthSkill(UserRepository repo) {this.repo = repo;}

    @Override
    public void checkPermission(User user, Resource res) {// 实现细节...}
}

TypeScript 推荐使用 InversifyJS:

const SKILL_TYPES = {Logging: Symbol.for('LoggingSkill')
};

@injectable()
class WinstonLogger implements LoggingSkill {// 实现代码...}

// 容器配置
const container = new Container();
container.bind<LoggingSkill>(SKILL_TYPES.Logging).to(WinstonLogger);

3. 自动化测试策略

采用双保险策略:

  1. 契约测试 :验证实现是否符合接口规范

    public class AuthSkillContractTest {
        @Test
        public void shouldThrowWhenResourceNotOwned() {AuthSkill skill = createSkillInstance();
            assertThrows(AccessDeniedException.class, 
                () -> skill.checkPermission(guestUser, premiumResource));
        }
    }

  2. 模拟测试 :验证 Skill 与上下游交互

    describe('ELKLogger', () => {it('should send log to daily index', async () => {const mockClient = { index: jest.fn() };
            const logger = new ELKLogger(mockClient);
    
            await logger.log('INFO', { event: 'test'});
    
            expect(mockClient.index).toHaveBeenCalledWith(
                expect.objectContaining({index: expect.stringMatching(/^logs-\\d{4}-\\d{2}-\\d{2}$/)
                })
            );
        });
    });

生产环境 Checklist

版本兼容

  • 遵循语义化版本控制 (SemVer)
  • 提供迁移指南(Migration Guide)
  • 维护变更日志(CHANGELOG.md)

监控埋点

@startuml
skinparam monochrome true

component "AuthSkill" as auth {[checkPermission]
}

[checkPermission] -> [Prometheus]: duration histogram
[checkPermission] -> [Sentry]: capture exception
@enduml

热更新方案

  1. 基于 Spring Cloud Config 的热配置
  2. Node.js 环境下使用模块热替换 (HMR)
  3. 兜底方案:优雅降级

开放性问题

当业务方提出特殊需求时,如何选择:

  1. 通过装饰器模式扩展现有 Skill
  2. 创建新的定制化 Skill 实现
  3. 使用策略模式支持多版本并存

这个决策需要权衡:

  • 变更频率:高频修改建议方案 3
  • 影响范围:核心流程建议方案 1
  • 团队共识:建立 Skill 治理规范

最后提醒: 过度抽象比重复代码更危险 。建议从真实痛点出发,逐步构建 Skill 生态。

正文完
 0
评论(没有评论)