一位荷兰程序员眼中的DDD - hexmaster


这里有一些关于DDD的想法。我真的很喜欢DDD(领域驱动设计)的思想和原则,我真的建议你去研究它。这就是为什么现在是新博客的时候了。我们称之为C#开发人员DDD的实用介绍。
这是系列的第一篇文章。这篇文章介绍了DDD以及如何构建领域模型。
那么,什么是DDD?您可能知道缩写的含义,但它究竟意味着什么?这个问题的答案很简单但很复杂。DDD是一件很大的事情,涉及很多东西,但基本上你只是在不同的域中划分系统的功能。在网上商店的经典示例中,目录,购物篮和订单流程都将位于单独的域中。这也可能是DDD和微服务如此良好结合的原因,但是利用DDD的强大功能并不一定意味着您的技术架构必须是微服务。您还可以在巨大的单体巨石中享受DDD的优势。
您在域中打包的所有功能称为有界上下文。在启动微服务架构时,您可能希望在单独的微服务中使用每个有界上下文,尽管并非所有情况都适用,请务必评估您的决策。现在在这个DDD世界中,还有一个人称为领域专家。这个人是指定域名中最聪明的人,可以告诉你关于它的一切。与敏捷/ Scrum相比,您可以将域专家识别为产品所有者,但是针对特定域。不同领域的专家实际上可能是一个人。这是您可能感到困惑的重要信息。拥有不同领域的不同专家也可能会引入术语上的差异。
正如词语所说,有界的上下文围绕它们有一个巨大的界限。这意味着域中涉及的所有功能和基础结构都与其他域分离。例如,不同的域不应共享相同的数据存储。在处理某个实体应该存在于多个域(例如用户和客户)的情况时,这可能会变得有点挑战,必须配置消息传递系统以同步域之间的更改。
当然,如果数据存储是分开的,这是推荐做法,最终的一致性非常重要。
一定要有一个好的解决方案。如果用户在用户服务中更改了他的电子邮件地址并下了订单,则您不希望订单服务向旧地址发送确认电子邮件。新的电子邮件地址应与订单服务同步,以便“知道”新地址。DDD的一个重要规则是只有一个域可以更改某个实体。因此,如果电子邮件地址属于用户,则只有用户服务可以更改它。所有域都可以使用用户的电子邮件字段,但只有一个可以更改它。

那对我来说有什么用呢?
DDD方式有什么优势?一个人知道的公司所有流程并不多。像亚马逊这样庞大的在线网络商店中,只有C级经理知道商品包装过程的细节,让这个人成为包装过程的领域专家是有道理的, 包装软件可能包含“包装过程人员”所知的术语和名称,域专家和软件解决方案之间没有翻译。
集中知识是关键,因为企业能够确保理解软件不会被锁定在“部落知识”中。这意味着有关软件功能的信息是开放的,每个人都可以做出贡献。开发人员(不再)是唯一知道整个业务流程的人。
最后,我认为与传统技术相比,使用DDD原理的精心设计的软件更容易维护。而且我经历了“牵一动百'经历。使用DDD以后所有活动组件仍然存在,但不再相互依赖。

第一个领域模型
基础很简单,我想为User用户创建一个域模型。用户具有ID,名称,电子邮件地址和密码。然后我还想跟踪创建和到期日期。所以我从几个属性开始:

public Guid Id { get; private set; }
public string DisplayName { get; private set; }
public string EmailAddress { get; private set; }
public Password Password { get; private set; }
public DateTimeOffset CreatedOn { get; private set; }
public DateTimeOffset ExpiresOn { get; private set; }

请注意,所有属性的setter都是私有的。这是为了防止外部系统更改属性的值。例如,您可能希望验证EmailAddress属性。如果EmailAddress属性是公共的(因此可以为其他类设置),则无法保证属性的值始终正确,因此您无法保证域模型的有效状态。所以相反,所有的setter都是私有的,所以没有人可以损害我们域模型的正确状态。现在要更改电子邮件地址,我们需要添加一个这样做的方法。

public void SetEmailAddress(string value)

    if(string.IsNullOrWhiteSpace(value))
    { 
        throw new ArgumentNullException(nameof(value)); 
    } 
    if(!Equals(EmailAddress,value))
    { 
        if(value.IsValidEmailAddress())
        { 
            EmailAddress = value; 
        } 
        else 
        { 
            throw new ArgumentException($“value {value}不是有效的电子邮件地址”); 
        } 
    } 
}

您可以看到电子邮件地址的所有验证(或此系统所需的验证)都是在SetEmailAddress()方法内完成的,而EmailAddress属性的有效性仅在新电子邮件地址根据业务规则有效时才会更改。顺便说一下,这些业务规则由域专家定义。

我认为域模型有两种构造函数,一种构造函数是创建一个新对象,例如一个用户。第二个是从(例如)数据存储重新生成现有对象。不同之处在于(在我看来):创建新用户时,您将最小的必填字段传递给构造函数以创建新的有效域模型。
在此示例中,用户的电子邮件地址是必需的。让我们说它是唯一的必填字段。然后,新用户的构造函数将只接受一个参数,即电子邮件地址。构造函数将创建域模型类的新实例,调用SetEmailAddress()方法来设置传递的电子邮件地址并返回新创建的对象。这样,电子邮件地址上的所有验证都会得到验证,以便在一切正常时:

public User(string emailAddress)

    Id = Guid.NewGuid(); 
    SetEmailAddress(EMAILADDRESS); 
    CreatedOn = DateTimeOffset.UtcNow ;; 
}

现在,如果您有关于用户的更多信息,让我们说出他的显示名称和密码,您可以创建其他Set方法,如SetEmailAddress()方法,验证传递的信息,然后在一切正常后立即更改属性值。您还看到我也添加了一些默认值。
现在,您可以将该用户传递到某个存储库,以便存储安全的地方。现在,如果您想要更改某个用户的信息,可以从数据存储中获取该信息并重现域模型。
此过程使用第二个构造函数。第二个构造函数接受域模型中的所有字段,并立即创建它。

public User(Guid id, 
    string emailAddress, 
    string displayName,
    Password pwd, 
    DateTimeOffset created,
    DateTimeOffset expires)
{
    Id = id;
    EmailAddress = emailAddress;
    DisplayName = displayName;
    Password = pwd;
    CreatedOn = created;
    ExpiresOn = expires;
}

所以我希望你现在正在思考,也许已经可以看到DDD的好处了。