spring和贫血域模型
所以,我注意到我绝对有这样的倾向:像我这样的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是否应该负责select何时保存自己很难。 如果您调用foo.setName(),是否应该坚持更改? 应该等到你叫foo.save()吗? foo.save()是否应该调用FooRepository.save(this)?
所以出于这些原因,我的Spring域对象往往基本上是带有一些validation逻辑的荣耀结构。 也许这没关系。 也许Web服务可以作为程序代码。 也许随着新function的写入,可以用新的方式创build新的服务来处理相同的旧对象。
但是我想逃避这种devise,我想知道Spring的其他用途呢? 你是否喜欢装载时织造(我不舒服)的花式技巧呢? 你有其他的诀窍吗? 你认为程序是好的吗?
您可以使用AOP将Spring注入到Hibernate实例化的实例中。 你也可以让Hibernate使用拦截器来做同样的事情。
见http://www.jblewitt.com/blog/?p=129
关于“让Foo事务性地做某些事情是烦人的”,我希望你的服务实现能够知道/关心事务,如果你现在在你的域模型中使用服务接口,那现在应该不是那么完美很烦人。
我怀疑决定何时应该保存域模型取决于它是什么以及你在做什么。
FWIW我倾向于产生同样的贫血结构,但我到了那里,现在我知道可以做一个更明智的方法。
听起来你的应用程序是围绕程序编码原则devise的。 这一点将阻碍你正在尝试做的任何面向对象的编程。
Foo可能没有它控制的行为。 如果业务逻辑很less,也可以不使用域模型模式。 事务脚本模式有时是有道理的。
当这个逻辑开始增长的时候,问题就出现了。 将交易脚本重构为域模型并不是最简单的事情,但它当然不是最困难的。 如果你有很多关于Foo的逻辑,我build议移动到Domain Model模式。 封装的好处使得很容易理解正在发生什么以及谁参与什么。
如果你想拥有Foo.Expire()
,在你的Foo类中创build一个事件,比如OnExpiration
。 连接你的foo.OnExpiration += FooService.ExpireFoo(foo.Id)
创build对象,可能通过FooRepository
使用的FooRepository
。
真的想想第一。 现在一切都已经到了正确的位置了。
祝你好运!
我认为有一个简单的重构模式可以解决你的问题。
- 注入你的服务到你的仓库。
- 在返回你的Foo之前设置它的'FooService
- 现在让你的FooController从FooRepository中请求合适的Foo
- 现在给你调用你想要的方法Foo。 如果它本身不能实现它们,请调用FooService上适当的方法。
- 现在,通过我喜欢在Foo上调用“bucket bridge”方法(它只是将parameter passing给服务),移除所有对FooService的调用。
- 从现在起,只要你想添加一个方法,就把它添加到Foo。
- 只有在出于性能原因需要时,才能将其添加到服务中。 像往常一样,这些方法应该通过模型对象来调用。
这将有助于将您发展为更丰富的领域模型。 它还保留了单一职责原则,因为所有依赖于DB的代码都保留在FooService实现中,并帮助您将业务逻辑从FooService迁移到Foo。 在你想把你的后台切换到另一个数据库或内存或模拟(testing)你不需要改变任何东西,但FooService层。
^我假设FooService做数据库调用将是太慢,从一个ORM,如select最近的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(); ... } }
我希望你会同意的,似乎更加丰富。 现在你正在做另一个数据库调用,但是如果你保持学校的caching,惩罚是可以忽略的。 我担心任何真正的OOP模型都不如您正在使用的贫血模型有效,但通过代码清晰度来减less错误应该是值得的。 一如既往,YMMV。