Martin Fowler的POEAA工作单位是反模式吗?

时间:2015-10-14 07:38:34

标签: oop orm domain-driven-design unit-of-work domain-model

从POEAA一书中,Martin Fowler介绍了这个工作单元的概念。如果你想拥有自动提交系统,你的域模型使用工作单元将自己标记为新的,脏的,删除的或清理的,它可以很好地工作。然后,您只需要调用UnitofWork.commit(),即可保存所有模型更改。以下是具有此类方法的域模型类:

public abstract class DomainModel{

    protected void markNew(){
        UnitOfWork.getCurrent().registerNew(this);
    }

    protected void markDirty(){
        UnitOfWork.getCurrent().registerDirty(this);
    }

    protected void markRemoved(){
        UnitOfWork.getCurrent().registerRemoved(this);
    }        

    protected void markClean(){
        UnitOfWork.getCurrent().registerClean(this);
    }
} 

通过此实现,您可以通过业务逻辑方法将域模型标记为任何保存状态:

public class Message extends DomainModel{

    public void updateContent(User user, string content){
        // This method update message content if the the message posted time is not longer than 24 hrs, and the user has permission to update messate content.
        if(!canUpdateContent(user) && timeExpired()) throw new IllegalOperationException("An error occurred, cannot update content.");
        this.content = content;
        markDirty();
    }  
}

乍一看,它看起来很奇妙,因为您不必在存储库/数据映射器上手动调用insert,save和delete方法。但是,我发现这种方法存在两个问题:

  1. 域模型与工作单元的紧密耦合:工作单元的这种实现将使域模型依赖于UnitOfWork类。 UnitOfWork必须来自某个地方,静态类/方法的实现很糟糕。为了改善这一点,我们需要切换到依赖注入,并将UnitOfWork的实例传递给Domain Model的构造函数。但这仍然将领域模型与工作单元联系起来。同样理想情况下,域模型应该只接受其数据字段的参数(即,消息域模型的构造函数应该只接受与消息相关的内容,例如标题,内容,日期发布等)。如果它需要接受UnitOfWork的参数,它将污染构造函数。

  2. 域模型现在变得持久感知:在现代应用程序设计中,特别是DDD,我们努力寻找持久无知的模型。域模型不应该关心它是否被持久化,它甚至不应该关心是否存在持久层。通过在域模型上使用那些markNew(),markDirty()等方法,我们的域模型现在有责任通知我们应用程序的其余部分需要持久化。虽然它不处理持久性逻辑,但模型仍然知道持久层的存在。我不确定这是不是一个好主意,对我而言似乎违反了单一责任原则。还有一篇文章谈到这个: http://blog.sapiensworks.com/post/2014/06/04/Unit-Of-Work-is-the-new-Singleton.aspx/

  3. 那你觉得怎么样? Martin Fowler中描述的原始工作单元模式是否违反了良好的OO设计原则?如果是这样,你认为它是反模式吗?

3 个答案:

答案 0 :(得分:7)

为了完全准确,没有人“Martin Fowler实施工作单位”。在书中,他区分了两种类型的修改对象注册为UoW。

来电者注册,其中只有调用对象知道UoW,并且必须将(被调用者)域对象标记为脏。 据我所知,没有反模式或不良做法

对象注册,域对象向UoW注册自己。这里有两个选择:

  

要使这个计划有效,工作单位需要传递给   对象或在一个众所周知的地方。通过工作单位   周围是乏味的,但通常没有问题让它出现在一些   会话对象。

代码示例正在使用UnitOfWork.GetCurrent(),它更接近后一个选项,并且因为紧密耦合的隐式依赖(服务定位器样式)而被公认为今天的反模式。

但是,如果选择了第一个选项,即将UoW传递给域对象,让我们假设一个工作单元抽象,这是不好的做法?从依赖管理的角度来看,显然不是。

现在仍然是持久性无知方面。我们可以说一个对象可以发出另一个对象的信号,它刚刚被编辑/创建/删除,它是持久性 - 意识到?非常值得商榷。 相比之下,如果我们查看更新的域对象实现,例如事件源中的实现,我们可以看到聚合can be responsible for keeping a list of their own uncommitted changes或多或少相同的想法。这会违反持久性无知吗?我不这么认为。

底线:Fowler选择说明其中一种UoW可能性的具体代码现在显然被认为是不好的做法,但对于您指出的问题#1更是如此,而不是真正的问题#2。这并没有取消他撰写的其他实现的资格,也没有取消整个UoW模式,其变更跟踪机制无论如何大部分时间都隐藏在第三方库魔术(如果读取:ORM)中,并且不像书中的例子那样硬编码。

答案 1 :(得分:3)

从DDD的角度来看,这是你不应该做的事情。

DDD包含以下规则:

  

应用程序服务应该只修改每个事务的一个聚合。

如果您遵循此规则,则可以清楚在应用服务操作期间更改了哪个聚合。然后,需要将此聚合传递到存储库以保存到DB:

repository.update(theAggregate);

无需其他电话。这会以你描述的形式击败模式的收益。

另一方面,您描述的模式引入了从域到持久性机制的依赖关系(取决于设计是真正的依赖还是仅仅是概念依赖)。 现在这是你应该避免的事情,因为它会大大增加模型的复杂性(不仅在内部,也为客户提供)。

因此,您不应该将这种形式的模式与DDD一起使用。

DDD之外

话虽如此,我认为这种模式是某个问题的众多解决方案之一。该解决方案有利有弊,其中一些您在问题中描述。在某些情况下,模式可能是最好的权衡,所以

不,这不是反模式。

答案 2 :(得分:0)

我不认为该模型不应该依赖于UoW。它更像是依赖于UoW的存储库,反过来,存储库将取决于模型。

如果你的存储库只依赖于一个抽象的UoW,那么唯一知道持久性技术的难题就是具体的UoW。

我倾向于允许模型依赖的唯一类是模型的其他部分:域服务,工厂等。