所以,我注意到我绝对倾向于模仿我的Spring / Hibernate堆栈对象:
这里的问题是Foos没有任何真正的逻辑。例如,如果每次Foo到期时都需要发送电子邮件,则不会调用Foo.expire()。有一个对FooService.expireFoo(fooId)的调用。这有多种原因:
因此,出于这些原因,我的Spring域对象往往是带有一些验证逻辑的美化结构。也许这没关系。也许Web服务可以作为程序代码。也许随着新功能的编写,创建以新方式处理相同旧对象的新服务是可以接受的。
但是我想摆脱这种设计,我想知道其他Spring使用的是什么呢?你是否用装载时编织等花哨的技巧来对抗它(我觉得不舒服)?你有其他的伎俩吗?你认为程序是好的吗?
答案 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)
我认为有一种简单的重构模式可以解决您的问题。
这将有助于您向更丰富的域模型发展。它还保留了单一责任原则,因为所有依赖于数据库的代码都保留在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时如何实现。