Spring和贫血域模型

lbsnaicq  于 2023-04-04  发布在  Spring
关注(0)|答案(4)|浏览(80)

所以,我注意到我肯定倾向于像这样模式化我的Spring/Hibernate堆栈对象:

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

这里的问题是Foo没有任何真实的的逻辑。例如,如果每次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的其他用法是怎么做的?你有没有用一些花哨的技巧来对付它,比如加载时编织(我不太习惯)?你有没有其他的技巧?你认为过程性的好吗?

omqzjyyz

omqzjyyz1#

您可以使用AOP让Spring将您的服务注入到Hibernate示例化示例中,也可以使用Interceptor让Hibernate做同样的事情。
参见https://web.archive.org/web/20131209103730/http://www.jblewitt.com/blog/?p=129
关于“让一个Foo在事务上做一些事情是很烦人的”,我希望你的服务实现会知道/关心事务,如果你现在在你的域模型中使用服务接口,现在应该不会那么烦人了。
我认为,决定何时保存域模型取决于它是什么以及您正在使用它做什么。
FWIW我有一种倾向,生产只是同样的贫血结构排序,但我得到了那里,现在我知道这是可能的,做一个更明智的方式。

4smxwvx5

4smxwvx52#

听起来你的应用程序是围绕过程化编码原则设计的,这本身就会阻碍你尝试进行的任何面向对象编程。
Foo可能没有它所控制的行为。如果您的业务逻辑最小化,那么 * 不 * 使用Domain Model模式也是可以接受的。Transaction Script模式有时只是有意义的。
当这种逻辑开始增长时,问题就出现了。将事务脚本重构为域模型不是最容易的事情,但肯定不是最困难的。如果你有大量的逻辑围绕着Foo,我建议转移到域模型模式。封装的好处使它很容易理解发生了什么以及谁参与了什么。
如果你想拥有Foo.Expire(),在你的Foo类中创建一个事件,比如OnExpiration。在创建对象时连接你的foo.OnExpiration += FooService.ExpireFoo(foo.Id),可能是通过FooRepository使用的工厂。
真的先想想。很有可能现在一切都已经在正确的地方了。
祝你好运!

41ik7eoe

41ik7eoe3#

我认为有一个简单的重构模式可以解决你的问题。
1.将服务注入到存储库中。
1.在返回Foo之前,请设置它的“FooService
1.现在让您的FooController从FooRepository请求适当的Foo
1.现在在Foo上调用你想要的方法。如果它自己不能实现它们,让它在FooService上调用适当的方法。
1.现在,通过Foo上我喜欢称之为“桶桥”的方法(它只是将参数沿着服务)删除对FooService的所有调用。
1.从现在开始,每当你想添加一个方法时,就把它添加到Foo中。
1.只有当你出于性能考虑而真正需要的时候才向服务添加东西。和往常一样,这些方法应该通过模型对象调用。
这将帮助您向更丰富的域模型发展。它还保留了单一责任原则,因为所有依赖于DB的代码都保留在FooService实现中,并帮助您将业务逻辑从FooService迁移到Foo。如果您想将后端切换到另一个DB或内存中或模拟(用于测试),您不需要更改任何东西,但FooService层。
^我假设FooService做的DB调用在ORM中做起来太慢了,比如选择最近的Foo,它与给定的Foo共享属性X。这是我见过的大多数工作方式。
示例
取代:

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();
        ...
    }
}

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

3duebb1j

3duebb1j4#

我向你推荐Doug Rosenberg和Matt Stephens的书Use Case Driven Object Modeling with UML。它讨论了ICONIX过程,一种软件开发方法,也讨论了贫血的领域模型。这也是Martin Fowler在其网站https://www.martinfowler.com/bliki/AnemicDomainModel.html上开发的一个主题。但是我们如何在使用Spring Framework和/或Sping Boot 时实现这一目标也是我试图弄清楚的。

相关问题