3

事件建模中的反模式:事件必须是对业务逻辑有价值的 - Oskar

 3 years ago
source link: https://www.jdon.com/57128
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
事件建模中的反模式:事件必须是对业务逻辑有价值的 - Oskar

当我们开始用事件建模我们的系统时,我们很容易掉入陷阱。我们习惯于从数据模型的角度来看待我们的功能:

当你手里拿着一个关系数据库时,你会看到到处都是表格,因为事件设计应该尽可能小,所以事件设计的第一个想法可以是,例如FirstNameChanged。另一个LastNameChanged等。我们也可以立即看到使用,即实体更改的直接历史。这些事件可能如下所示:

public class FirstNameChanged
{
    public string FirstName { get; }
    public DateTime ChangedAt { get; }

    public FirstNameChanged(string firstName, DateTime changedAt)
    {
        FirstName = firstName;
        ChangedAt = changedAt;
    }
}

public class LastNameChanged
{
    public string LastName{ get; }
    public DateTime ChangedAt { get; }

    public LastNameChanged(string lastName, DateTime changedAt)
    {
        LastName = lastName;
        ChangedAt = changedAt;
    }
}

然后我们可以直接在 UI 中创建对审计跟踪的更改历史记录。

public class FirstNameChanged
{
    public string PreviousFirstName { get; }
    public string NewFirstName { get; }
    public DateTime ChangedAt { get; }

    public FirstNameChanged(string previousFirstName,  string newFirstName, DateTime changedAt)
    {
        PreviousFirstName = previousFirstName;
        NewFirstName = newFirstName;
        ChangedAt = changedAt;
    }
}

public class LastNameChanged
{
    public string PreviousLastName { get; }
    public string NewLastName { get; }
    public DateTime ChangedAt { get; }

    public LastNameChanged(string previousLastName,  string newLastName, DateTime changedAt)
    {
        PreviousLastName = previousLastName;
        NewLastName = newLastName;
        ChangedAt = changedAt;
    }
}

即使通过观察这些事件,我们也可以闻到令人不快的气味。很容易看出这种方法是不可维护的。当我们的模型增长时,我们会得到许多微小的、复制/粘贴的、毫无意义的事件。

事件建模的关键方面是让它们接近业务。事件应该直接对应于系统中业务操作的结果。

为了实现这一点,事件应该从特定的请求/命令处理中派生出来。根据命令中发送的值,我们知道传输了哪些数据。基于它们和业务逻辑,我们可以填写事件数据。

名称已更改的事实通常不是影响业务逻辑的因素。(CRUD)

通常,我们只是接受更改,填写数据,仅此而已。因此,我们可以在事件中传递这些信息,并在投影中使用它来构建读取模型。但是,即使我们有 Jiralike 表单来编辑特定字段,也值得根据特征对此类更改进行分组。

我们可以通过更新名字、姓氏等来触发PersonalDataUpdated事件。这些字段可能有Option 类型来检查它们是否已更改。C# 中此类类型的示例实现可能如下所示:

public struct Option <T>
{
    public static Option <T> None => default;
    public static Option <T> Some (T value) => new Option <T> (value);

    readonly bool isSome;
    readonly T value;

    Option (T value)
    {
        this.value = value;
        isSome = this.value is {};
    }

    public bool IsSome (out T value)
    {
        value = this.value;
        return isSome;
    }
}

那么用法如下:

public class PersonalDataUpdated
{
    public Option<string> FirstName { get; }
    public Option<string> LastName { get; }
    public DateTime ChangedAt { get; }

    public PersonalDataUpdated(string firstName,  string lastName, DateTime changedAt)
    {
        PreviousLastName = previousLastName;
        NewLastName = newLastName;
        ChangedAt = changedAt;
    }
}

var onlyLastNameUpdated = 
    new PersonalDataUpdated(
        Option<string>.None, 
        Option<string>.Some("Smith")
    );

有了这个,我们没有数百个小事件,而是业务重大事件

它们仍然包含更改内容的详细信息。我们可以基于它们创建读取模型。假设我们要使用有关先前值和当前值的信息来构建审计跟踪。在这种情况下,我们可以在投影中检索模型的最后状态,将其与事件的变化进行比较,并将差异保存为新行。

发布诸如LastNameChanged 之类的事件称为Property Sourcing。

这是一种反模式。事件本身并没有告诉我们执行它们的操作。它们没有商业价值。由于我们必须生成的事件类型数量众多,管理它们也具有挑战性。其他模块使用它们也不方便。

当然,有时创建字段更改事件是有意义的。例如,EmailUpdated、MartialStateChanged、AccountBalanceUpdated、InvoiceNumberSet。这些是重要的业务领域,可以触发其他工作流程。

良好的事件建模的基础是与业务合作。讨论和理解我们想要达到的目标是基础。当然,有时,停止设计讨论并开始编码是值得的。当我们看到它们在行动时,更容易找到弱点。尽管如此,我们不应该试图通过两周的编码来节省四个小时的讨论时间。

同样重要的是不要将我们的初始事件模型视为一成不变。我们应该接受我们的模型将会改变。我们会更好地了解我们的领域。随着时间的推移,业务也会发生变化。我们应该继续向下钻取,使我们的事件模型更接近现实世界。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK