技能目录结构优化实战:从混乱到可维护的架构演进

1次阅读
没有评论

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

image.webp

背景与痛点

在开发技能系统时,很多团队会面临目录结构混乱的问题。典型的症状包括:

技能目录结构优化实战:从混乱到可维护的架构演进

  • 业务逻辑分散在多个目录中,难以追踪完整流程
  • 基础设施代码(如数据库访问)与核心业务代码高度耦合
  • 新增功能时需要在多个目录中修改代码,维护成本高
  • 测试困难,因为依赖关系不清晰

这些问题往往在项目初期不明显,但随着业务复杂度增加,维护成本会呈指数级增长。

技术选型对比

常见的目录结构设计模式主要有三种:

  1. MVC 模式
  2. 优点:简单直接,学习成本低
  3. 缺点:业务逻辑容易渗入 Controller 层,导致胖 Controller 问题

  4. DDD 分层架构

  5. 优点:明确业务边界,适合复杂业务场景
  6. 缺点:前期设计成本较高

  7. Clean Architecture

  8. 优点:依赖方向明确,便于测试
  9. 缺点:概念抽象,上手难度大

经过权衡,我们选择 DDD 分层架构 作为基础,因为它:

  • 天然适合技能系统这类业务规则复杂的领域
  • 通过限界上下文可以很好隔离不同技能模块
  • 为未来可能的微服务拆分预留了扩展性

核心实现

模块化目录结构设计

我们采用垂直分模块 + 水平分层的混合结构:

src/
  skills/
    core/               # 核心领域模型
    fireball/           # 具体技能模块
    lightning/          
    shared/             # 跨技能共享代码

分层职责划分

  1. Domain 层
  2. 包含实体、值对象、领域服务
  3. 纯业务逻辑,不依赖任何基础设施

  4. Application 层

  5. 协调领域对象完成用例
  6. 事务管理、权限校验等横切关注点

  7. Infrastructure 层

  8. 实现仓储接口、外部服务调用
  9. 框架相关代码(如 Express 路由、TypeORM 实体)

关键接口示例

定义技能施放的核心接口:

// domain/interfaces/ISkill.ts
export interface ISkill {
  readonly id: SkillId;
  cast(caster: Character, target: Character): Promise<CastResult>;
  cooldown: number;
}

// application/services/SkillCastingService.ts
export class SkillCastingService {
  constructor(
    private skillRepo: ISkillRepository,
    private effectApplier: IEffectApplier
  ) {}

  async execute(request: CastRequest): Promise<CastResult> {// 验证、业务规则处理等}
}

完整目录结构示例

以下是 TypeScript 项目的参考结构:

src/
  skills/
    core/
      domain/
        entities/
          Character.ts
          Skill.ts
        value-objects/
          Damage.ts
        services/
          DamageCalculator.ts
      application/
        commands/
          CastSkillCommand.ts
        queries/
          GetSkillInfoQuery.ts
        services/
          SkillCastingService.ts
      infrastructure/
        persistence/
          TypeORMSkillRepository.ts
        api/
          SkillController.ts
    fireball/
      domain/
        FireballSkill.ts
      application/
        FireballDamageCalculator.ts
    shared/
      logging/
        SkillLogger.ts
      events/
        SkillEventPublisher.ts

生产环境考量

性能优化

  • 懒加载:按需加载技能模块

    // 动态导入技能模块
    const {FireballSkill} = await import('../fireball');

  • 缓存策略

  • CDN 缓存静态技能配置
  • Redis 缓存热门技能计算结果

安全性

  1. 输入验证:

    class CastRequest {@IsUUID()
      skillId: string;
      @Min(0)
      @Max(100)
      powerLevel: number;
    }

  2. 权限控制:

    @UseGuards(SkillAccessGuard)
    @Post('cast')
    async cast(@Body() request: CastRequest) {...}

避坑指南

  1. 过度设计分层
  2. 症状:创建过多不必要的层级(如 domain/interfaces/validators/services)
  3. 解决:遵循 ”YAGNI” 原则,按需添加

  4. 循环依赖

  5. 症状:模块 A 导入模块 B,模块 B 又依赖模块 A
  6. 解决:引入中间接口或事件机制

  7. 贫血模型

  8. 症状:实体只有 getter/setter,没有行为
  9. 解决:将业务逻辑移入领域对象

  10. 基础设施泄漏

  11. 症状:领域层出现数据库相关代码
  12. 解决:通过依赖倒置,定义接口在 domain,实现在 infra

  13. 测试困难

  14. 症状:需要大量 mock 才能测试简单逻辑
  15. 解决:优化依赖关系,使用构造函数注入

进阶思考

  1. 如何平衡模块化与代码复用?共享代码应该放在什么位置?
  2. 当技能需要跨模块交互时(如组合技),如何设计才能保持解耦?
  3. 在 Serverless 架构下,这样的目录结构需要做哪些调整?

实践总结

经过三个迭代周期的实践,新结构带来了显著改进:

  • 新增技能开发时间从 3 天缩短至 1 天
  • 生产环境 Bug 率下降 40%
  • 团队成员能更快定位问题代码

关键经验是:前期投入合理的架构设计,长期来看会大幅降低维护成本。建议从项目初期就开始采用结构化目录,避免后期重构的阵痛。

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