春天和贫血领域模型

时间:2009-08-20 06:06:03

标签: spring oop

所以,我注意到我绝对倾向于模仿我的Spring / Hibernate堆栈对象:

  • Foo控制器调用“FooService”
  • FooService调用FooRepository.getById()方法来获取一些Foos。
  • FooRepository调用一些Hibernate来加载Foo对象。
  • FooService与Foos进行一些互动。它可以使用相关的TransactionalFooService来处理需要在事务中一起完成的事情。
  • FooService要求FooRepository保存Foos。

这里的问题是Foos没有任何真正的逻辑。例如,如果每次Foo到期时都需要发送电子邮件,则不会调用Foo.expire()。有一个对FooService.expireFoo(fooId)的调用。这有多种原因:

  • 从Foo获取其他服务和对象很烦人。它不是Spring bean,它是由Hibernate加载的。
  • 让Foo在事务上做几件事很烦人。
  • 很难确定Foo是否应该负责选择何时自救。如果你调用foo.setName(),foo应该保持更改吗?它应该等到你调用foo.save()吗? foo.save()应该只调用FooRepository.save(this)吗?

因此,出于这些原因,我的Spring域对象往往是带有一些验证逻辑的美化结构。也许这没关系。也许Web服务可以作为程序代码。也许随着新功能的编写,创建以新方式处理相同旧对象的新服务是可以接受的。

但是我想摆脱这种设计,我想知道其他Spring使用的是什么呢?你是否用装载时编织等花哨的技巧来对抗它(我觉得不舒服)?你有其他的伎俩吗?你认为程序是好的吗?

4 个答案:

答案 0 :(得分:12)

您可以使用AOP将Spring注入您的Hibernate实例化实例。你也可以使用拦截器让Hibernate做同样的事情。

请参阅http://www.jblewitt.com/blog/?p=129

关于“让Foo在事务上做几个事情很烦人”,我希望您的服务实现能够了解/关心事务,如果您现在使用域模型中的服务接口,那么现在应该不太讨厌。

我怀疑决定何时保存域模型取决于它是什么以及你正在做什么。

FWIW我倾向于产生同样的贫血结构,但我已经到了那里,现在我知道可以以更明智的方式做到这一点。

答案 1 :(得分:9)

听起来您的应用程序是围绕过程编码原则设计的。仅这一点就会阻碍你想要做的任何面向对象的编程。

Foo可能没有它控制的行为。如果您的业务逻辑很小,那么使用Domain Model模式也是可以接受的。 Transaction Script模式有时候才有意义。

当逻辑开始增长时,问题就出现了。将事务脚本重构为域模型并不是最简单的事情,但它肯定不是最困难的事情。如果你有很多围绕Foo的逻辑,我建议你转移到域模型模式。封装的好处使得它很容易理解发生了什么以及谁参与了什么。

如果您想拥有Foo.Expire(),请在您的Foo类中创建一个事件,例如OnExpiration。关于创建对象的foo.OnExpiration += FooService.ExpireFoo(foo.Id),可能通过FooRepository使用的工厂。

真的先考虑一下。 非常可能一切都已经在正确的位置......现在。

祝你好运!

答案 2 :(得分:4)

我认为有一种简单的重构模式可以解决您的问题。

  1. 将您的服务注入您的存储库。
  2. 在返回你的Foo之前​​设置'FooService
  3. 现在让你的FooController从FooRepository
  4. 中询问相应的Foo
  5. 现在调用你想要的方法Foo。如果它本身无法实现它们,请让它在FooService上调用适当的方法。
  6. 现在通过我在Foo上称为“桶桥”方法的方法删除对FooService的所有调用(它只是将参数传递给服务)。
  7. 从现在开始,只要您想添加方法,请将其添加到Foo。
  8. 出于性能原因,确实需要时才向服务添加内容。与往常一样,应该通过模型对象调用这些方法。
  9. 这将有助于您向更丰富的域模型发展。它还保留了单一责任原则,因为所有依赖于数据库的代码都保留在FooService实现中,并帮助您将业务逻辑从FooService迁移到Foo。如果您想将后端切换到另一个DB或内存或模拟(用于测试),则除了FooService层之外,您不需要更改任何内容。

    ^我假设FooService执行的DB调用从ORM中执行的速度太慢,例如选择与给定Foo共享属性X的最新Foo。这就是我见过最多的工作。


    实施例

    而不是:

    class Controller{
        public Response getBestStudentForSchool( Request req ){
            Student bestStudent = StudentService.findBestPupilForSchool( req.getParam( "schlId" ).asInt() );
            ...
        }
    }
    

    你会走向这样的事情:

    class Controller{
        public Response getBestStudentForSchool( Request req ){
            School school = repo.get( School.class, req.getParam( "schlId" ).asInt() ); 
            Student bestStudent = school.getBestStudent();
            ...
        }
    }
    

    我希望你会同意我看来已经更加富有了。现在您正在进行另一个数据库调用,但如果您将学校缓存在会话中,则惩罚可以忽略不计。我担心任何真正的OOP模型都会比你使用的贫血模型效率低,但通过代码清晰度减少错误应该是值得的。一如既往,YMMV。

答案 3 :(得分:0)

我向您推荐Doug Rosenberg和Matt Stephens撰写的《 用UML进行用例驱动的对象建模》 。它讨论了ICONIX过程的软件开发方法,也讨论了贫血领域模型。这也是Martin Fowler在其网站https://www.martinfowler.com/bliki/AnemicDomainModel.html中开发的主题。但是,我在尝试使用Spring Framework和/或Spring Boot时如何实现。