aggreate root具有复合主键的存储库

时间:2015-05-07 21:01:41

标签: c# domain-driven-design repository-pattern aggregateroot eventual-consistency

存储库应该作为聚合根的边界,即IRepository<TAggreagte>将提供CRUD功能,以事务方式将数据保存到数据库。到目前为止一切都很好。

但是如果聚合具有复合主键怎么办? 在我的问题中,它是一个Identity INT 列加上一个 SMALLINT 序列号。 (这是数据库设计,不是我的想法!)

我见过的存储库示例有例如void Add(TAggregate aggregate)bool Add(TAggregate aggregate)

使用“最终一致性”的示例:

我想添加聚合A ,我需要调用存储库A ,然后插入依赖 聚合B 使用存储库B ,插入后我必须知道聚合A 的ID。

这就是我迷失的地方。如果你插入A,你会如何得到它的ID,特别是如果它是一个复合键? 我看到的唯一解决方案是再次返回整个对象,所以:

TAggregate Add(TAggregate a);

有什么建议吗?

3 个答案:

答案 0 :(得分:2)

在DDD中,身份是一个非常棘手的话题。

关于身份创造的“时机”,有两种思想流派:

  • 在创建该实体类的实例时生成标识,或
  • 标识在持久化时创建(例如,当它插入关系数据库时)。

第二种方法可能会导致很多问题。你已经提出的一个。当仅在持久性时建立身份时,还会出现一些其他真正的问题。考虑域实体的这两个核心属性:

  • 实体以其身份而着称。因此,没有身份就不能存在实体。
  • 当身份相同时,实体被视为平等。

使用“持久性身份”方法创建实体类的新实例时,您最初没有身份,因此违反了上述所有原则。身份现在是否在您的实体中建模为可选的?在我看来,这种方法会让你走上一条非常黑暗的道路。

有效解决这些问题的唯一方法是在实例化时生成身份。这也将解决您的问题。我们会立即为您提供身份证明。

如果您的数据库技术自动生成ID,这可能会非常棘手。

有几种方法可以在实体实例化时生成标识。

在实体中生成身份:

简单示例:

public Person : DomainEntity<Guid>
{
    //..
    public Person(string name)
      : base(Guid.New()) // Providing a unique GUID
    {
        Name = name;
    }
}

客户代码:

// A new person with identity!
var person = new Person("Eric Evans"); 

这是首选方法,但并非总是可行。

您向实体提供身份:

简单示例:

public Person : DomainEntity<int>
{
    //..
    public Person(int identity, string name)
      : base(identity) // Providing a unique GUID
    {
        Name = name;
    }
}

客户代码:

// A new person with identity!
var person = new Person(IdentityGenerator.Create(), "Eric Evans");

IdentityGenerator生成器可能与您的数据库交互以获取并保留“下一个”标识(不幸的是,SQL Server不支持这种标识)。

你站在哪里?

关于您是否有复合密钥,您需要提出的问题是 “我是否能够在实例化时生成或提供实体身份?”

答案 1 :(得分:1)

  

但是如果聚合有一个复合主键怎么办?在我的问题中,它是一个Identity INT列加上一个SMALLINT序列号。 (这是数据库设计,不是我的想法!)

聚合永远不会有复合主键,因为

  1. Aggregate是一组对象。实体即聚合根,具有 Id ,其可以是确保Id是全局的格式的值对象(提示:Guid)。 Id可以是对域有意义的自然ID,例如某些生成的数字或字母和数字的组合(通常由域服务生成)。
  2. 主键是一个RDBMS概念,一个持久性细节。您的Db设计应考虑实体的Id格式。如果您的数据库架构是首先设计的,或者与域需求无关,那么您做错了,即您实际上并没有进行域驱动设计。
  3.   

    我想添加一个聚合A,我需要调用存储库A,然后使用存储库B插入一个依赖聚合B,我必须知道聚合A插入后的ID。

    交易型CRUD思维模式在这里并不起作用,尽管工作单元概念是持久性细节但如果在更高级别使用anti-pattern。解决方案非常简单:服务将创建并持久化Aggregate A然后发布将包含id(创建了A)的域事件。另一个服务将充当事件处理程序,并将创建/持久聚合B.根据技术实现的方式,最终的一致性将非常短。

    为获得可靠的结果,应使用耐用的服务总线。

答案 2 :(得分:0)

Key is a value object - it's its value that matters, the format is just a technical detail. You could create a <Team id="team1"> <Players> <Player id="player1"/> <Player id="player2"/> <Player id="player3"/> <Player id="player4"/> </Players> <Captain ref="player3"/> </Team> class (PersonKey, OrderItemKey...) to hold the key value

class Team
{
    public List<Player> Players { get; set; }

    [XmlReference]
    public Player Captain { get; set; }
}

and then use it as follows:

TAggregateKey