oop - Martin Fowler 的工作单元的 POEAA 实现是反模式吗?

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

好吧,Martin Fowler 从 POEAA 一书中介绍了这种工作单元的概念。如果您想拥有自动提交系统,它会非常有效,在该系统中,您的域模型使用工作单元将自己标记为新的、脏的、已删除的或干净的。然后你只需要调用 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();
    }  
}

乍一看,它看起来很棒,因为您不必在存储库/数据映射器上手动调用插入、保存和删除方法。但是,我发现这种方法存在两个问题:
  • 域模型与工作单元的紧密耦合:工作单元的这种实现将使域模型依赖于 UnitOfWork 类。 UnitOfWork 必须来自某个地方,静态类/方法的实现很糟糕。为了改善这一点,我们需要切换到依赖注入(inject),并将 UnitOfWork 的实例传递给 Domain Model 的构造函数。但这仍然将域模型与工作单元结合在一起。理想情况下,域模型应该只接受其数据字段的参数(即消息域模型的构造函数应该只接受与消息相关的内容,例如标题、内容、发布日期等)。如果它需要接受 UnitOfWork 的参数,它将污染构造函数。
  • 领域模型现在变得具有持久感知能力:在现代应用程序设计中,尤其是 DDD,我们力求实现持久无知模型。领域模型不应该关心它是否被持久化,它甚至不应该关心是否有持久层。通过在域模型上使用这些 markNew()、markDirty() 等方法,我们的域模型现在有责任通知应用程序的其余部分它需要被持久化。虽然它不处理持久化逻辑,但模型仍然知道持久层的存在。我不确定这是否是个好主意,对我来说这似乎违反了单一责任原则。还有一篇文章谈到这个:
    http://blog.sapiensworks.com/post/2014/06/04/Unit-Of-Work-is-the-new-Singleton.aspx/

  • 所以你怎么看? Martin Fowler 中描述的原始工作单元模式是否违反了良好的 OO 设计原则?如果是这样,你认为它是一种反模式吗?

    最佳答案

    完全准确地说,没有“Martin Fowler 的工作单元实现”。在书中,他区分了将修改对象注册到 UoW 的两种类型。

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

    对象注册域对象向 UoW 注册自己的位置。这里再次有两个选项:

    For this scheme to work the Unit of Work needs either to be passed to the object or to be in a well-known place. Passing the Unit of Work around is tedious but usually no problem to have it present in some kind of session object.



    代码示例使用 UnitOfWork.GetCurrent()由于紧密耦合、隐式依赖(服务定位器样式),它更接近于后一种选择,并且在今天被广泛认为是一种反模式。

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

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

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

    关于oop - Martin Fowler 的工作单元的 POEAA 实现是反模式吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33119379/

    相关文章:

    PHP DDD如何命名入口点方法?

    domain-driven-design - 微服务事件驱动架构的设计选择

    matlab - 如何在面向对象的 Matlab 中定义派生属性

    PHP: 'method doesnt exist' ,但确实如此

    php - 如何使用 Propel ORM 来 Zend Framework

    java - 在 JPA @GeneratedValue 列中手动指定主键的值

    java - 服务层和领域模型的问题

    c# - 如何将信息从一个类传递到另一个类

    java - 如何从适用于多个类类型的方法返回实际类型而不是对象类型?

    php - 向 Propel 模型添加自定义列?