核心概念

在了解工具之前,我们先了解一些基础的核心概念,这有助于理解工具的设计和使用。

了解应该程序开发

先限定好范围,我们以下讨论的是对外提供接口服务的Web API应用,这是当前使用最广泛的一种方式,也是本工具适用的范围。

作为开发者,我们可以先梳理一下,从0到1开发一个系统的步骤,使用不同语言不同框架,不同习惯的人可能有所差异,这里我以使用ASP.NET CoreEF Core为例。

  1. 通过模板创建新项目。
  2. 定义实体模型(迁移同步的数据库)
  3. 编写逻辑代码(如数据库/缓存/读写文件/调用第三方接口等各种操作),获取处理结果
  4. 编写控制器代码(获取请求,执行逻辑代码,将结果返回)

这里的核心就是定义实体模型,DRY工具也是围绕它进行设计的。

理解实体模型

实体模型是用来表示业务模型的,可以说,不同的业务系统之间,使用什么语言框架和技术架构实现并不重要,它们不能区分出不同的业务。真正决定业务的不同就是抽象出来的业务模型,我们一般称为实体模型领域模型,就是说通过模型定义的内容,你大概就能理解你做的是什么业务。

当开发者拿到一个需求时,首先我要理解需求中涉及到的实体,然后思考要用什么数据结构和表示它,用什么样的存储介质来存储和查询它。

举个简单的例子:

现在我要开发一个非常简单的博客系统,让我能够编写,修改和删除博客。

根据这个需求,我们可以抽象出来两个实体:

  • 用户,管理自己的博客内容
  • 博客,可以被创建,修改,删除和查询

而它们之间的关系是:一个用户可以管理多个博客。

得益于EF CoreCode First,我们可以不必关心数据库结构,而是直接使用代码来表示业务模型,由此我们将使用定义两个实体模型,如:

public class User
{
    public required string UserName { get; set; }
}
/// <summary>
/// 博客
/// </summary>
public class Blog
{
    /// <summary>
    /// 标题
    /// </summary>
    public required string Title { get; set; }
    public string? Content { get; set; }
    /// <summary>
    /// 创建时间
    /// </summary>
    public DateTimeOffset CreatedTime { get; set; } = DateTimeOffset.UtcNow;
    public DateTimeOffset? UpdatedTime { get; set; }
}

以上是两个非常简单的类,在定义时,你可以看到:

  • 以///开头的注释:表示属性的含义
  • required:表示该属性是必须的
  • ?:表示该属性是可空的
  • =DateTimeOffset.UtcNow;:表示该属性的默认值是当前时间

得益于C#优秀的表现力,定义以上内容比手动创建数据库表要更容易。

如果只是如此定义,它只是普通的模型类,并没有体现实体的含义。用户和博客这两个实体之间有是关系的,让我们来加一些实体的内含:

[Index(nameof(UserName), IsUnique = true)]
public class User
{
    [Key]
    public Guid Id { get; set; }
    [MaxLength(20)]
    public required string UserName { get; set; }
    /// <summary>
    /// 拥有的博客
    /// </summary>
    public List<Blog> Blogs { get; set; } = [];
}
/// <summary>
/// 博客
/// </summary>
[Index(nameof(Title), IsUnique = true)]
public class Blog
{
    [Key]
    public Guid Id { get; set; }
    /// <summary>
    /// 标题
    /// </summary>
    [MaxLength(100)]
    public required string Title { get; set; }
    [MaxLength(10_000)]
    public string? Content { get; set; }
    /// <summary>
    /// 创建时间
    /// </summary>
    public DateTimeOffset CreatedTime { get; set; } = DateTimeOffset.UtcNow;
    public DateTimeOffset? UpdatedTime { get; set; }
    /// <summary>
    /// 所属用户
    /// </summary>
    public required User User { get; set; }
}

我们添加了以下元素:

  • Id:表示实体的唯一标识,用于区分不同的实体,其中[Key]表示主键。
  • [Index]:表示索引,最终影响存储时的数据结构,其中的IsUnique = true表示该属性的值是唯一的。
  • [MaxLength]:表示该属性的最大长度,用于限制输入的内容。
  • List<Blog> Blogs:表示用户拥有的博客,用一个集合来表示。
  • required User User:表示博客所属的用户,required表示博客必须拥有一个用户,不能单独存在。

通过以上添加的代码,我们对实体添加了更多的定义,包括它们之间的一对多关系,以及一些约束条件,这样我们就可以更好的理解这两个实体的含义。

当然,我们可以进一步完善实体的定义,以表示出用户可以添加修改删除博客的操作,在领域驱动设计中,通常会将这些操作定义在实体中,这样仅仅通过实体表达的信息, 我们就能理解业务的含义。

代码生成

本工具的代码生成就是根据实体模型来生成代码。

得益于C#强大且优雅的表达力,我们可以将我们的业务意图通过实体模型定义。然后借助微软官方的Roslyn解析和理解语义,从而理解最原始的业务意图,然后根据这些意图来生成业务代码。

生成DTO

DTO作为数据传输对象,起到了桥梁的作用,DTO也属性定义的范畴,通过DTO的内容定义,我们能了解到更多的业务意图。为了满足常规的CURD操作,

工具会生成以下DTO:

Dto 作用
ItemDto 列表元素
DetailDto 某实体的详情
FilterDto 请求筛选条件模型
AddDto 添加时的模型
UpdateDto 更新时的模型

Note

关于DTO生成的策略和规则,可查看DTO生成

生成Manager

Manager用来实现业务逻辑,通过实体可以生成一个Manager,生成的Manager默认包含以下方法:

  • 添加实体
  • 更新实体
  • 获取实体详情
  • 获取实体列表
  • 删除实体
  • 实体唯一性判断

生成控制器

控制器用来接收请求,进行数据验证,通过调用Manager的方法处理逻辑,然后将处理后的结果返回。生成的控制器包含以下接口方法:

  • 添加实体
  • 更新实体
  • 获取实体详情
  • 获取实体列表(分页)
  • 删除实体

这也就是说,对于任意一个实体,我们可以通过工具一键生成完整的CURD功能,在此基础上,你可以根据实际业务需求去调整和扩展其他方法。

总结

基于实体模型的代码生成是本工具的核心,其本质是尝试理解业务的含义,然后在此基础上生成代码。

引入AI

后续,工具将探索将大语言模型引入到工具中,从而通过实体模型理解更多的业务意图,从而生成更多更有效的代码。