工作单元模式如何容纳对新聚合的引用?

时间:2011-01-20 04:18:20

标签: c# design-patterns domain-driven-design unit-of-work atomicity

背景

据我了解,工作单元(UoW)模式本质上提供了事务语义。换句话说,给定存储库持久存在的聚合域,UoW类允许域的使用者将存储库方法的调用注册到原子操作中。说我们有:

interface IAggregate<TKey> {
    TKey Id { get; }
}

interface IRepository<TEntity, in TKey> where TEntity : IAggregate<TKey> {
    TEntity Get(TKey id);
    void Save(TEntity entity);
    void Remove(TEntity entity);
}

interface IUnitOfWork {
    void RegisterSave<TEntity>(TEntity entity);
    void RegisterRemove<TEntity>(TEntity entity);
    void RegisterUnitOfWork(IUnitOfWork uow);
    void Commit();
    void Rollback();
}

假设IRepository的实现使用关系数据库,IUnitOfWork.Commit的实现仅仅与数据库建立事务并继续调用SaveRemove适用于已注册的所有操作的IRepository个实例。我要说的是我上面概述的是对聚合根,存储库和UoW模式的标准直接解释(尽管NHibernate / EF及其所有臃肿的荣耀)。

过去,我已经将聚合根边界的概念解释为从一个聚合到另一个聚合的引用应该被源聚合上的目标聚合的Id属性客观化。例如:

class User : IAggregate<int> {
  int Id { get; private set; }
}

class Blog : IAggregate<int> {
  int Id { get; private set; }
  int AuthorUserId { get; set; }
}

问题

鉴于上述关注点的分离和聚合边界的解释,如何为事务上需要创建聚合并将其存储库生成的Id保存在另一个聚合中的消费者提供事务支持?例如。如何在设置为User的{​​{1}}的情况下创建BlogBlog.UserId交易?

我已经提出了一些答案(标记为社区维基),但无论如何我在这里发布我的问题以征求反馈和更多答案。

3 个答案:

答案 0 :(得分:1)

  

鉴于上述关注点的分离和聚合边界的解释,如何为事务上需要创建聚合并将其存储库生成的Id保存在另一个聚合中的消费者提供事务支持?例如。如何在Blog.UserId设置为User.Id?

的情况下以事务方式创建用户和博客

事情是 - 聚合负责绘制事务边界。这意味着 - 如果博客创建以某种方式失败,则不需要同时创建用户博客和回滚用户创建。

如果有这样的需要 - 你正在对聚合错误建模。


只需发布一些可能有用的快速评论......

interface IAggregate<TKey> {
    TKey Id { get; }
}

接口应该用于定义行为(角色),而不是实现类将保持什么(在这种情况下有关密钥类型的知识)。

不能快速google以找到正确的解释为什么确实如此...... 稍后再试。

interface IRepository<TEntity, in TKey> where TEntity : IAggregate<TKey> {
  TEntity Get(TKey id);
  void Save(TEntity entity);
  void Remove(TEntity entity);
}

避免使用generic repositories

interface IUnitOfWork {
  void RegisterSave<TEntity>(TEntity entity);
  void RegisterRemove<TEntity>(TEntity entity);
  void RegisterUnitOfWork(IUnitOfWork uow);
  void Commit();
  void Rollback();
}

避免使用unit of work(这个解决了你的问题)。

interface IAggregate<TSelf> where TSelf : IAggregate<TSelf>
{
    IKey<TSelf> Id { get; }
}

继续学习。很快你就会停止滥用界面&amp;仿制药。 :)

答案 1 :(得分:0)

ID类型应该更智能作为参考类型吗?这样,当存储库更新ID时,可以通过另一个聚合的引用来访问它。例如:

interface IKey<TAggregate> : IEquatable<IKey<TAggregate>>
{
    /* should provide a ctor that accepts a string */

    TAggregate GetAggregateType();
    string ToString();
    bool IsAssigned { get; }
}

interface IAggregate<TSelf> where TSelf : IAggregate<TSelf>
{
    IKey<TSelf> Id { get; }
}

interface IRepository<TAggregate> where TAggregate : IAggregate<TAggregate>
{
    TAggregate Get(IKey<TAggregate> id);
    void Save(TAggregate entity);
    void Remove(TAggregate entity);
}

class User : IAggregate<User>
{
    public IKey<User> Id { get; private set; }
}

class Blog : IAggregate<Blog>
{
    public IKey<Blog> Id { get; private set; }
    public IKey<User> Author { get; private set; }
}

IKey<TAggregate>的实现与IRepository<TAggregate>的实现在同一个包中提供。

答案 2 :(得分:0)

似乎必须放松以下模式约束之一:

  • 存储库负责生成聚合ID。相反,必须使用自然键。
  • UoW无法注册任意Action个代表。相反,允许UoW注册任意Actions
  • 聚合间关系被物化为ID属性。而是使用聚合类型属性,例如:

partial class Blog : IAggregate<int> { 
  User Author { get; set; } 
}

我是否错误地解释了模式?或者他们在这方面实际上是受限制的吗?