Spring与贫血域模型

Spring与贫血域模型,spring,oop,Spring,Oop,因此,我注意到我确实有一种倾向,我的Spring/Hibernate堆栈对象的模式如下: class Controller{ public Response getBestStudentForSchool( Request req ){ School school = repo.get( School.class, req.getParam( "schlId" ).asInt() ); Student bestStudent = school.getBe

因此,我注意到我确实有一种倾向,我的Spring/Hibernate堆栈对象的模式如下:

class Controller{
    public Response getBestStudentForSchool( Request req ){
        School school = repo.get( School.class, req.getParam( "schlId" ).asInt() ); 
        Student bestStudent = school.getBestStudent();
        ...
    }
}
  • Foo控制器调用“FooService”
  • FooService调用FooRepository.getById()方法来获取一些Foo
  • FooRepository进行一些Hibernate调用以加载Foo对象
  • FooService与Foos进行一些交互。它可以使用相关的TransactionalFooService来处理需要在事务中一起完成的事情
  • FooService要求FooRepository保存Foos
这里的问题是Foos没有任何真正的逻辑。例如,如果每次Foo过期时都需要发送电子邮件,则不会调用Foo.expire()。有一个对FooService.expireFoo(fooId)的调用。原因有很多:

  • 从Foo获取其他服务和对象很烦人。它不是SpringBean,它是由Hibernate加载的
  • 让一个Foo做一些事务性的事情是很烦人的
  • 很难决定Foo是否应该负责选择何时拯救自己。如果调用foo.setName(),foo是否应该保持更改?它是否应该等到调用foo.save()时再运行?foo.save()是否应该调用FooRepository.save(这个)
因此,出于这些原因,我的Spring域对象基本上是带有一些验证逻辑的美化结构。也许这没关系。也许web服务可以作为过程代码。也许随着新特性的编写,可以接受创建以新方式处理相同旧对象的新服务


但我想避开这种设计,我想知道其他Spring的用途是什么?你有没有用一些花哨的技巧来对付它,比如在加载时编织(我不太习惯)?你还有别的把戏吗?您认为过程性很好吗?

您可以让Spring使用AOP将您的服务注入到您的Hibernate实例化实例中。您还可以使用拦截器让Hibernate执行同样的操作

关于“让一个Foo以事务方式做一些事情很烦人”,我希望您的服务实现会知道/关心事务,如果您现在正在域模型中使用服务接口,那么现在应该不会那么烦人了

我怀疑,决定何时保存域模型取决于它是什么以及您正在使用它做什么


FWIW我倾向于产生相同类型的贫血结构,但我已经做到了,现在我知道可以用一种更合理的方法来实现它。

听起来你的应用程序是围绕程序编码原则设计的。这将阻碍您尝试进行的任何面向对象编程

Foo可能没有它控制的行为。如果您的业务逻辑最小,那么不使用模式也是可以接受的。一个模式有时是有意义的

当这种逻辑开始增长时,问题就出现了。将事务脚本重构为域模型不是最容易的事情,但肯定不是最困难的。如果您有大量关于Foo的逻辑,我建议您转到域模型模式。封装的好处使得理解发生了什么以及谁参与了什么变得非常容易

如果您想让
Foo.Expire()。可能通过
foosrepository
使用的工厂,在对象创建时连接
foo.onexpatition+=FooService.ExpireFoo(foo.Id)

先好好想想。很有可能一切都已经准备就绪了。。。现在


祝你好运

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

  • 将服务注入到存储库中
  • 在返回Foo之前,请设置其“FooService”
  • 现在让您的FooController从FooRepository中请求适当的Foo
  • 现在在你的Foo上调用你想要的方法。如果它不能自己实现它们,让它调用FooService上的适当方法
  • 现在,通过我喜欢在Foo上调用的“bucket bridge”方法删除对FooService的所有调用(它只是将参数传递给服务)
  • 从现在起,无论何时您想要添加一个方法,都要将它添加到Foo
  • 仅当出于性能原因确实需要时才向服务添加内容。与往常一样,应该通过模型对象调用这些方法
  • 这将帮助您向更丰富的领域模型发展。它还保留了单一责任原则,因为所有依赖DB的代码都保留在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模型的效率都会低于您正在使用的贫血模型,但是通过代码清晰性减少bug应该是值得的。一如既往,YMMV.

    我向您推荐Doug Rosenberg和Matt Stephens的《使用UML进行用例驱动的对象建模》一书。它讨论了ICONIX过程——一种软件开发方法,也讨论了贫血领域模型。这也是Martin Fowler在其网站上开发的一个主题。但我们如何实现usi