域驱动设计 – 通过复合标识将DDD导航到聚合根内的实体

我有一个聚合的根产品,其中包含一个实体选择列表,它们又包含称为“特征”的实体列表.

>聚合根产品具有刚刚名称的身份
>实体选择具有名称的身份(及其对应的产品身份)
>实体特征具有名称的身份(也是相应的选择标识)

实体的身份如下构建:

var productId = new ProductId("dedisvr");
var selectionId = new SelectionId("os",productId);
var featureId = new FeatureId("windowsstd",selectionId);

请注意,依赖身份将父类的身份作为组合的一部分.

这个想法是,这将形成可以通过选择中的特定特征来识别的产品部件号,即上述featureId对象的ToString()将返回dedisvr-os-windowsstd.

所有产品都存在于产品聚合中,其中业务逻辑用于对选择和功能之间的关系执行不变性.在我的域中,功能在没有选择的情况下存在并且没有关联产品的选择没有意义.

当查询产品的相关功能时,将返回Feature对象,但是C#internal关键字用于隐藏可能使实体发生变化的任何方法,从而确保实体对于调用应用程序服务是不可变的(在与代码不同的程序集中).

这两个上述断言由两个功能提供:

class Product
{
    /* snip a load of other code */

    public void AddFeature(FeatureIdentity identity,string description,string specification,Prices prices)
    {
       // snip...
    }

    public IEnumerable<Feature> GetFeaturesMemberOf(SelectionIdentity identity);
    {
       // snip...
    }
}

我有一个称为服务订单的聚合根,它将包含一个ConfigurationLine,它将引用FeatureId在Product聚合根中的Feature.这可能是一个完全不同的有限的上下文.

由于FeatureId包含SelectionId和ProductId字段,因此我将知道如何通过聚合根导航到该功能.

我的问题是:

复合身份形成与父母的身份 – 好或坏的做法?

在其他身份被定义为类的其他样本DDD代码中,我还没有看到由本地实体id及其父身份形成的任何复合体.我认为这是一个不错的属性,因为我们可以随时浏览到该实体(总是通过聚合根),知道要到达的路径(Product – > Selection – > Feature).

虽然我的代码与组合身份链与父母是有道理的,并允许我通过根聚合导航到实体,没有看到其他代码示例,其中身份形成与复合材料类似,使我非常紧张 – 任何原因或这是不好的做法?

参考内部实体 – 暂时性或长期性?

bluebook提到对集合中的实体的引用是可以接受的,但应该只是暂时的(在代码块中).在我的情况下,我需要存储这些实体的引用以供将来使用,存储不是暂时的.

然而,存储此引用的需要仅用于报告和搜索,即使我确实想要检索通过根目录的子实体,返回的实体是不可变的,所以我看不到可以做任何损害或不变量破碎.

我的思维是否正确,如果是这样,为什么它提到保持子实体引用暂时性?

源代码如下:

public class ProductIdentity : IEquatable<ProductIdentity>
{
    readonly string name;

    public ProductIdentity(string name)
    {
        this.name = name;
    }

    public bool Equals(ProductIdentity other)
    {
        return this.name.Equals(other.name);
    }

    public string Name
    {
        get { return this.name; }
    }

    public override int GetHashCode()
    {
        return this.name.GetHashCode();
    }

    public SelectionIdentity NewSelectionIdentity(string name)
    {
        return new SelectionIdentity(name,this);
    }

    public override string ToString()
    {
        return this.name;
    }
}

public class SelectionIdentity : IEquatable<SelectionIdentity>
{
    readonly string name;
    readonly ProductIdentity productIdentity;

    public SelectionIdentity(string name,ProductIdentity productIdentity)
    {
        this.productIdentity = productIdentity;
        this.name = name;
    }

    public bool Equals(SelectionIdentity other)
    {
        return (this.name == other.name) && (this.productIdentity == other.productIdentity);
    }

    public override int GetHashCode()
    {
        return this.name.GetHashCode();
    }

    public override string ToString()
    {
        return this.productIdentity.ToString() + "-" + this.name;
    }

    public FeatureIdentity NewFeatureIdentity(string name)
    {
        return new FeatureIdentity(name,this);
    }
}

public class FeatureIdentity : IEquatable<FeatureIdentity>
{
    readonly SelectionIdentity selection;
    readonly string name;

    public FeatureIdentity(string name,SelectionIdentity selection)
    {
        this.selection = selection;
        this.name = name;
    }

    public bool BelongsTo(SelectionIdentity other)
    {
        return this.selection.Equals(other);
    }

    public bool Equals(FeatureIdentity other)
    {
        return this.selection.Equals(other.selection) && this.name == other.name;
    }

    public SelectionIdentity SelectionId
    {
        get { return this.selection; }
    }

    public string Name
    {
        get { return this.name; }
    }

    public override int GetHashCode()
    {
        return this.name.GetHashCode();
    }

    public override string ToString()
    {
        return this.SelectionId.ToString() + "-" + this.name; 
    }
}

Composite identities formed with identity of parent – good or bad practice?

当他们被正确使用时,他们是一个很好的做法:当域名专家识别本地的东西(例如“营销的约翰”)时,它们是正确的,否则就是错误的.

一般来说,每当代码遵循专家的语言,这是正确的.

有时候,当他谈到一个具体的有界背景时,你会面临一个全球确定的实体(如“约翰·史密斯”).在这些情况下,BC要求获胜.
请注意,这意味着您需要一个域服务才能在BC之间映射标识符,否则所有您需要的是shared identifiers.

References to internal entities – transient or long term?

如果聚合根(在您的情况下为Product)要求子实体确保业务不变量,则引用必须是“长期”,至少在不变量必须成立之前.

此外,您正确地掌握了内部实体背后的理由:如果专家识别这些实体,则它们是实体,可变性是一种编程问题(而不变性总是更安全).您可以拥有不可变的实体,无论是本地还是其他实体,但是它们是实体的事实,即专家识别它们,而不是它们的不变性.

价值对象是不可变的,因为他们没有身份,而不是其他方式!

但是当你说:

However the need to store this reference is for reporting and searching purposes only

我建议你使用直接的SQL查询(或者是用DTO查询对象,或者你可以使用的任何东西),而不是域对象.报告和搜索不会突变实体的状态,因此您不需要保留不变量.这是CQRS的主要理由,它只是意味着:“仅当您必须确保业务不变量时才使用域模型!使用WTF您喜欢只需要阅读的组件!”

额外的笔记

When querying the product for associated features,the Feature object is returned but the C# internal keyword is used to hide any methods that could mutate the entity…

如果您不需要单元测试客户端,但是如果您需要测试客户端代码(或引入AOP拦截器或其他任何东西),那么访问修饰符在这种情况下处理修饰符是一种便宜的方法. .

有人会告诉你,你正在使用“不必要的抽象”,但使用语言关键字(interface)并不意味着引入抽象!
我不完全确定他们真正了解what an abstraction is,他们混淆了抽象行为的工具(OO中常见的一些语言关键字).

抽象存在于程序员的头脑(在专家的心中,在DDD中),代码只是通过您使用的语言提供的结构来表达它们.

密封类混凝土?结构具体吗?没有!!!
你不能让他们伤害无能的程序员!
它们与接口或抽象类一样多抽象.

抽象是不必要的(更糟糕的是,它是危险的),如果它使代码的散文不可读,难以遵循,等等.但是,相信我,它可以被编码为密封类!

… and thus ensure the entity is immutable to the calling application service (in a different assembly from domain code).

呃,你也应该考虑到,如果由总计返还的“显然是不可变的”的本地实体实际上可以改变其国家的一部分,那么收到他们的客户将无法知道这种变化发生了.

对我来说,我通过返回(也是内部使用)实际上是不可变的本地实体来解决这个问题,迫使客户端仅持有对总根(也称主实体)和subscribe events on it的引用.

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


什么是设计模式一套被反复使用、多数人知晓的、经过分类编目的、代码 设计经验 的总结;使用设计模式是为了 可重用 代码、让代码 更容易 被他人理解、保证代码 可靠性;设计模式使代码编制  真正工程化;设计模式使软件工程的 基石脉络, 如同大厦的结构一样;并不直接用来完成代码的编写,而是 描述 在各种不同情况下,要怎么解决问题的一种方案;能使不稳定依赖于相对稳定、具体依赖于相对抽象,避免引
单一职责原则定义(Single Responsibility Principle,SRP)一个对象应该只包含 单一的职责,并且该职责被完整地封装在一个类中。Every  Object should have  a single responsibility, and that responsibility should be entirely encapsulated by t
动态代理和CGLib代理分不清吗,看看这篇文章,写的非常好,强烈推荐。原文截图*************************************************************************************************************************原文文本************
适配器模式将一个类的接口转换成客户期望的另一个接口,使得原本接口不兼容的类可以相互合作。
策略模式定义了一系列算法族,并封装在类中,它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
设计模式讲的是如何编写可扩展、可维护、可读的高质量代码,它是针对软件开发中经常遇到的一些设计问题,总结出来的一套通用的解决方案。
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中,使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
迭代器模式提供了一种方法,用于遍历集合对象中的元素,而又不暴露其内部的细节。
外观模式又叫门面模式,它提供了一个统一的(高层)接口,用来访问子系统中的一群接口,使得子系统更容易使用。
单例模式(Singleton Design Pattern)保证一个类只能有一个实例,并提供一个全局访问点。
组合模式可以将对象组合成树形结构来表示“整体-部分”的层次结构,使得客户可以用一致的方式处理个别对象和对象组合。
装饰者模式能够更灵活的,动态的给对象添加其它功能,而不需要修改任何现有的底层代码。
观察者模式(Observer Design Pattern)定义了对象之间的一对多依赖,当对象状态改变的时候,所有依赖者都会自动收到通知。
代理模式为对象提供一个代理,来控制对该对象的访问。代理模式在不改变原始类代码的情况下,通过引入代理类来给原始类附加功能。
工厂模式(Factory Design Pattern)可细分为三种,分别是简单工厂,工厂方法和抽象工厂,它们都是为了更好的创建对象。
状态模式允许对象在内部状态改变时,改变它的行为,对象看起来好像改变了它的类。
命令模式将请求封装为对象,能够支持请求的排队执行、记录日志、撤销等功能。
备忘录模式(Memento Pattern)保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。 基本介绍 **意图:**在不破坏封装性的前提下,捕获一个对象的内部状态,并在该
顾名思义,责任链模式(Chain of Responsibility Pattern)为请求创建了一个接收者对象的链。这种模式给予请求的类型,对请求的发送者和接收者进行解耦。这种类型的设计模式属于行为
享元模式(Flyweight Pattern)(轻量级)(共享元素)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结