跳到主要内容

基础设施与分层

FreeKitModules 使用轻量、务实的 DDD。依赖方向以 Controller → Application Service → Domain Manager/Repository 为主,但仓库保留了若干历史结构,不能把每个模块描述成完全相同的四层模板。

当前项目边界

模块CoreHTTP 接入结构特点
CmsKitFreeKit.CmsKitFreeKit.CmsKit.HttpApiDomain 与 Application 按 Content、Engagement、Discovery 等子域组织,最接近标准 DDD
IdentityFreeKit.IdentityFreeKit.Identity.InfrastructureFreeKit.Identity.HttpApiInfrastructure 同时承载文件、设置、公共附件及其历史 Controller
MemberFreeKit.MemberFreeKit.Member.HttpApi以 Application、Contracts、Models 为主,当前没有独立 Domain/Manager 目录
PlatformFreeKit.PlatformFreeKit.Platform.HttpApiHoliday、ShortUrl、ToDo 是垂直切片,其余能力仍有 Application 内混合结构

新代码应向清晰边界收敛,但不要为了满足目录模板而移动无关存量代码。

各层职责

HttpApi

HttpApi 项目只处理 HTTP/SignalR:

  • Controller 路由、模型绑定和状态码。
  • [Authorize][AllowAnonymous]、权限 policy 或 KitAuthorize
  • Swagger GroupName
  • SignalR Hub、Hub Filter 和 HTTP 专属服务替换。

Controller 应依赖 Application Service,而不是注入 IFreeSql 或直接调用 Domain Manager。FreeKitController 只提供 [ApiController] 和默认 [Authorize],不承载业务逻辑。

Application

Application Service 编排一个用户用例:

  • 获取当前用户/租户。
  • 校验权限和资源归属。
  • 调用一个或多个 Manager、Repository 或外部端口。
  • 控制事务、事件发布、DTO 映射和返回结果。

需要共享能力时继承 ApplicationService;通用 CRUD 可继承 CrudAppService<...>。CmsKit 的 ArticleService、Identity 的 PermissionService、Platform 的 ToDoService 都是当前源码范本。

Domain

Domain 层包含实体行为和跨实体业务规则。命名约定为:

类型接口实现
领域服务IXxxManagerXxxManager
应用服务IXxxServiceXxxService

领域服务接口继承 IDomainService,实现通常继承 DomainService。例如 IArticleManager / ArticleManager 负责文章可评论性、可见性和互动计数规则。

实体可以把局部不变量封装成行为。实际的 ToDo 实体提供:

public void MarkDone(bool isDone)
{
IsDone = isDone;
DoneTime = isDone ? DateTime.Now : null;
}

public bool CanBeAccessedBy(Guid userId)
{
return CreateUserId == userId;
}

这是存量实现;新写入时间必须使用 DateTime.UtcNow,不要复制这里的 DateTime.Now

Infrastructure / Data

以下代码可以直接依赖 IFreeSql

  • FreeSql 注册、AOP 和全局过滤器。
  • Module Startup 中的 CodeFirst/迁移初始化。
  • Repository、DbContext、Schema Migrator 等明确的数据基础设施。

Application 和 Controller 新代码不得直接注入 IFreeSql。复杂查询应封装在审计仓储之上的查询对象、专用 Repository 或 Domain Manager 中。

审计实体

FullAuditEntityFullAuditEntity<Guid,Guid> 的简写;泛型版本的第一个参数是实体主键类型,第二个参数是用户主键类型:

public class KitFile : FullAuditEntity<long, long>, ITenant
{
public Guid? TenantId { get; set; }
public string Name { get; set; } = string.Empty;
public string Path { get; set; } = string.Empty;
}

它真实包含:

  • Id
  • CreateUserIdCreateUserNameCreateTime
  • UpdateUserIdUpdateUserNameUpdateTime
  • DeleteUserIdDeleteUserNameDeleteTime
  • IsDeleted

租户实体另外实现 ITenant 并提供 Guid? TenantId。不要把所有实体都假设为 Guid 主键;KitFile、导航等模块使用 long

仓储边界

FreeKit 提供两个审计仓储接口:

public interface IAuditBaseRepository<TEntity>
: IBaseRepository<TEntity, Guid>
where TEntity : class;

public interface IAuditBaseRepository<TEntity, TKey>
: IBaseRepository<TEntity, TKey>
where TEntity : class;

使用方式与 FreeSql Repository 一致:

ToDo? todo = await todoRepository.Select
.Where(x => x.Id == id)
.FirstAsync();

if (todo is not null)
{
todo.MarkDone(true);
await todoRepository.UpdateAsync(todo);
}

这里的 ToDotodoRepository 对应 FreeKit.ToDos.Application.ToDoService 中的真实实体与 IAuditBaseRepository<ToDo>

审计行为由 AuditBaseRepository<TEntity,TKey> 在 Insert/Update/Delete 前处理:

  • 实现创建审计接口时补充创建人和创建时间。
  • 实现更新审计接口时补充修改信息。
  • 实现 ISoftDelete 或删除审计接口时,DeleteAsync 转为更新软删除字段。

只有通过审计仓储或其派生实现写入,才能获得这些行为;直接使用 IFreeSql.Insert/Update/Delete 不会自动经过 Repository 的审计钩子。

FreeSql 全局行为

AddDefaultFreeSql 在 Web BuildingBlock 中统一配置:

  • 实体属性名转下划线命名。
  • ISoftDelete 全局过滤。
  • ITenant 租户过滤。
  • 审计、敏感词和 SQL Activity AOP。
  • UnitOfWorkManager 与开放泛型 IAuditBaseRepository

数据库类型由 ConnectionStrings:DefaultDB 解析为 FreeSql DataType,再读取对应连接串。DefaultDB: "0" 表示 MySQL,不是内存数据库。

事务

工作单元拦截器扫描名称以 Service 结尾的公开类。需要显式事务的应用方法使用现有 [Transactional]

[Transactional]
public override async Task DeleteAsync(Guid id)
{
Article article = await articleRepository.Select
.Where(x => x.Id == id)
.FirstAsync();

await articleRepository.DeleteAsync(article);
await relatedRepository.DeleteAsync(x => x.ArticleId == id);
}

CAP 与数据库事务联合提交时,可通过 ApplicationService.UnitOfWorkManagerICapPublisher 建立 CAP Transaction;范本见 FreeKit.Identity.Application.Profiles.ProfileService.SetAvatarAsync

Autofac 扫描与显式注册

UseFreeKitmoduleTypeMapextraAssemblies 收集到的程序集执行:

  • UnitOfWorkModule:注册 *Service
  • RegisterDomainServices:注册 IDomainService
  • FreeKitModule:注册三个依赖标记接口。

所以主 Host 必须把 Core 程序集传入 extraAssemblies;只注册 HttpApi Startup 会发现 Controller,却可能漏掉 Core 中的应用服务和领域服务。

需要明确顺序或实现替换的服务仍在 ConfigureServices 中显式注册,不要完全依赖扫描。

已知历史例外

  • Identity.Infrastructure 含文件、设置和公共附件 Controller。
  • Platform 的部分模型和 Manager 位于 Application 子目录。
  • HolidayServiceDailyHotJobService 以及少量 CmsKit Application Service 仍直接访问 IFreeSql
  • Member 当前没有独立 Domain 层。
  • 多处存量代码仍使用 DateTime.Now 或异步方法缺少 Async 后缀。

这些是渐进重构边界,不是新代码范本。具体守护规则见 .github/guidelines/conventions.md

源码索引

  • src/FreeKit/src/IGeekFan.FreeKit.Extras/AuditEntity/FullAuditEntity.cs
  • src/FreeKit/src/IGeekFan.FreeKit.Extras/FreeSql/IAuditBaseRepository.cs
  • src/FreeKit/src/IGeekFan.FreeKit.Extras/FreeSql/AuditBaseRepository.cs
  • src/BuildingBlocks/IGeekFan.FreeKit.Web/Extensions/ServiceCollectionExtensions.cs
  • src/BuildingBlocks/IGeekFan.FreeKit.Web/DI/ContainerBuilderExtensions.cs
  • src/BuildingBlocks/IGeekFan.FreeKit.Infrastructure/Application/AppliactionService.cs
  • src/BuildingBlocks/IGeekFan.FreeKit.Infrastructure/DDD/DomainService.cs

相关文档