kepawni/serge

从您的 GraphQL 模式生成 CQRS/ES 模型类。

v0.7 2021-11-17 21:38 UTC

This package is auto-updated.

Last update: 2024-09-18 04:28:41 UTC


README

编写您的 GraphQL 模式,让 serge 生成您的

  • 命令处理器
  • 聚合
  • 事件类型
  • 值对象

用于 CQRS 和事件源。

快速开始

运行 vendor/bin/serge-codegen,并注意默认配置出现在您的当前工作目录中。更改设置并再次运行脚本。这将为您提供一个示例 GraphQL 模式进行测试。使用它来定义您的模型,从现在开始(同时放置两个文件),再次调用 vendor/bin/serge-codegen 将为您生成构成域模型的类文件。

重要! 使用 Git 或其他代码版本控制,因为这些类将在每次调用代码生成器时被覆盖。或者,您可以在配置文件中更改目标目录,并仔细地将类复制到您的位置。

GraphQL 模式

要使此功能正常工作,让我们从我们的 GraphQL 模式的最小需求开始。我们将关注命令端,因此我们将查询部分保持最小。实际上,建议为查询端使用另一个面向 Web 的端点。

schema {
    query: CqrsQuery
    mutation: CqrsAggregateMutators
}
type CqrsQuery {
    status: Boolean!
}

查询部分必须在有效的 GraphQL 模式中存在,因此我们定义 CqrsQuery(顺便说一句,您可以随意命名它)为一个常规的 类型。为了满足类型至少有一个字段的条件,我们定义 状态,这样我们就有一个简单的方法来测试我们的端点是否正常工作。这可以捕获很多问题,如缺少依赖项、错误的配置路径或服务器实例没有按计划启动。

现在对于 突变 部分,它由另一个名为 CqrsAggregateMutators类型 覆盖(再次,如果您喜欢,可以更改该名称)。当我们定义此 类型 时,我们为模型中的每个聚合添加一个字段

声明聚合

type CqrsAggregateMutators {
    Customer(id: ID!): Customer!
    Invoice(id: ID!): Invoice!
}

这些字段需要一个参数来传递聚合 ID,因此我们使用 id: ID! 作为硬编码的约定。这些字段的返回类型必须与它们的名称匹配,且不可为空,这也是一个固定的约定。这样,我们现在已经声明了模型中可用的聚合,并可以转向它们应该处理的命令。

为每个聚合定义命令

因此,我们为每个聚合定义另一个 类型,并将命令作为字段添加。

type Invoice {
    chargeCustomer(customerId: ID!, invoiceNumber: String, invoiceDate: Date): ID!
    appendLineItem(item: LineItem!): Boolean!
    correctMistypedInvoiceDate(invoiceDate: Date!): Boolean!
    overrideDueDate(dueDate: Date!): Boolean!
    removeLineItemByPosition(position: Int!): Boolean!
}

返回类型遵循另一个简单的约定

  • ID! 用于工厂命令,该命令将新的聚合引入到存在状态
  • Boolean! 用于常规命令,该命令将现有聚合进行变更

字段名称应该是小写,因为这些将是此聚合提供的公共方法。

定义值对象

您可能会注意到,这些命令同时使用内置类型(例如 String)和复杂但未定义的类型,如 LineItem。这些是我们的值对象,这就是我们如何将它们定义为 输入 类型

input LineItem {
    quantity: Float!
    price: Money!
    title: String
}
input Money {
    amount: Float!
    currency: String!
}

如您所见,它们甚至可以相互引用,形成一个复杂且灵活的类型系统。

通过这些步骤,我们已经定义了代码生成器构建模型类所需的所有内容,但既然值对象可以如此轻松地定义,为什么不用这个机制来定义域事件呢?

定义事件类型

interface InvoiceEvents {
    CustomerWasCharged(customerId: ID!, invoiceNumber: String, invoiceDate: Date): Boolean!
    LineItemWasAppended(item: LineItem!): Boolean!
    MistypedInvoiceDateWasCorrected(invoiceDate: Date!): Boolean!
    DueDateWasOverridden(dueDate: Date!): Boolean!
    LineItemWasRemoved(position: Int!): Boolean!
}

为了区分值对象和事件,我们在本处使用接口类型,而另一种约定要求我们将基于发出它们的聚合事件分组,这在名称(聚合名称 + Events)中体现出来。这次,字段名称以大写字母开头,因为这些将被转换为事件类,参数将是事件的属性。这里Boolean!的返回类型只是另一种约定。

XML配置

上面的GraphQL模式定义了我们模型的内部结构,但为了生成实际的类文件,我们需要更多关于路径和命名空间等上下文信息。这个差距将通过文件serge.config.xml(名称是固定的)来弥补。

如果您是第一次调用代码生成器,当没有此类配置文件时,它将为您生成一个。请参阅快速入门指南,了解如何从头开始。