开发规范
本文整理的是当前仓库可执行的开发约定。涉及目录和注册方式时,以
src/Services/Host/FreeKit.Host/Program.cs
及各模块的 *ModuleStartup.cs 为准;历史代码仍有少量例外,不应把例外复制到新代码。
分层与命名
主业务模块采用务实的 DDD 分层,而不是要求每个子域都有完全相同的目录:
| 层 | 职责 | 命名 |
|---|---|---|
Domain/ | 聚合、领域规则和需要持久化协作的领域服务 | IXxxManager / XxxManager |
Application/ | 用例编排、权限、DTO 转换、事务协作 | IXxxService / XxxService |
Controllers/ 或 *.HttpApi | HTTP 协议、路由和响应包装 | XxxController |
Infrastructure/ | 数据库迁移、外部实现和基础设施适配 | 按能力命名 |
面向用户的 API 不直接暴露 Domain 内部服务。Controller 调用 Application Service;复杂查询和持久化通过 Domain Manager 或仓储封装。
Controller 保持薄
- 继承
FreeKitController;它默认带授权要求。 - 只有明确允许游客访问的 Action 才标记
[AllowAnonymous]。 - Controller 只处理路由参数、协议转换和
ApiResponse,业务规则留在 Application / Domain。 - Controller Action 是异步命名的例外,可以不使用
Async后缀;其他返回Task的业务方法应使用Async。
[Area("cms")]
[Route("api/cms/articles")]
[ApiController]
public class ArticleController(IArticleService articleService) : FreeKitController
{
[HttpGet("{id:guid}")]
public Task<ArticleDto> Get(Guid id) => articleService.GetAsync(id);
}
Application Service 基类
Application Service 继承 ApplicationService。基类采用延迟解析,已经提供:
CurrentUser、PermissionStore、AuthorizationServiceLogger/LoggerFactoryCapPublisher、MediatorUnitOfWorkManager/CurrentUnitOfWork- 本地化访问器
L
因此不要仅为了取得这些对象再次注入同一依赖。对“查看本人或凭权限查看指定用户”的列表类接口,可使用基类的 GetEffectiveUserIdAsync(permission, queryUserId);资源级修改仍必须根据实体所有者单独校验,不能把列表权限检查当成资源授权。
数据访问边界
Application 和 Controller 新代码不得依赖 FreeSql.IFreeSql,也不得直接写 fsql.Select<T>()。优先使用:
IAuditBaseRepository<TEntity>
IAuditBaseRepository<TEntity, TKey>
需要跨聚合规则或复杂查询时,在仓储之上建立 Domain Manager 或专用查询对象。仓库中仍有少量直连 IFreeSql 的历史债务;它们是迁移清单,不是示例。PlatformArchitectureTests 会守护 Platform 的大部分 Application / Controller 边界,Holiday 暂列为显式豁免。
异常与设置
| 场景 | 异常 |
|---|---|
| 业务规则不满足、资源不存在、业务状态不允许 | BusinessException |
| 服务未注册、系统配置缺失或程序状态不可能成立 | InvalidOperationException |
设置项在 *SettingDefinitionProvider.cs 定义,通过 ISettingManager.GetSystemOrNullAsync(name) 读取系统值。数据库设置应按请求读取以支持热更新,不要复制到静态缓存。JSON 设置可用 WithJsonSchemaType(typeof(TSettings)) 提供编辑模型,但不要标记为加密,否则保存后的密文不能直接反序列化为 JSON。
外部 HTTP 与序列化
外部调用使用 AddHttpClient<TClient, TImplementation>(),并通过 AddStandardResilienceHandler 配置超时、重试和熔断。Polly V8 要求熔断采样窗口 SamplingDuration 至少是单次尝试超时的两倍;长耗时操作还要避免一次完整超时后再次产生昂贵请求。Platform.TTS 的 MimoTtsClient 是当前参考实现。
API 边界由 Web 构建块统一使用 Newtonsoft.Json;API DTO 不应依赖仅 System.Text.Json 能识别的特性。仅用于外部 HTTP 的内部模型可以使用 System.Text.Json。
时间与提交前检查
新写入的数据库时间戳使用 DateTime.UtcNow,展示时再转换时区。不要一次性替换历史 DateTime.Now,因为这可能改变既有数据含义。
提交前至少确认:
- Controller 没有业务规则或 ORM 查询。
- Application / Domain 的异步方法以
Async结尾。 - 资源级读写同时检查身份、权限和所有权。
- 新配置没有静态缓存,敏感值没有写进示例文档。
- 外部 HTTP 有统一弹性管线。
- 运行与改动范围相匹配的构建和测试;参见测试与验证。