将后备存储与Spring Web MVC分离

时间:2009-12-05 11:37:49

标签: java model-view-controller spring jpa

这是一个设计模式问题,所以我将通过一个地址簿应用程序的简单示例来说明它。

首先是一些假设。 1.直接使用DB域对象作为Spring MVC表单的后备存储似乎是可以接受的。

我的应用程序的迭代1我创建了一个附加了各种属性的JPA映射Person对象。使用DAO模式,我创建了一个持久性对象,可以从数据库中获取,存储和删除人员。另外我有一个工厂方法,创建,所以我可以得到一个人对象。使用这个DAO对象,我创建了一个简单的Web前端。一切都很好。

在迭代2中,我需要支持多种存储类型,因此我为person创建了一个接口,它具有多个实现,以及DAO持久性的接口,同样具有多个实现。此外,人员被扩展为能够拥有多个地址。

interface IPerson {
    public String getName();
    public List<IAddress> getAddresses();
}

但是,当涉及到更新Web界面以便能够处理这些多个实现时,我遇到了问题。持久性实现由Spring注入。并且,因为持久性对象具有工厂方法,所以我非常适合创建IPerson实现。但是,如果我想做一些奇特的事情,比如允许多个地址作为一个请求的一部分提交,那么我就有问题了。为了使它能够与Spring一起使用,你似乎需要使用一个AutoPopulatingList,所以spring只需要.get(#)记录一个副本的属性。

因此,使这项工作的一个解决方案是要求所有持久性实现使用自动填充列表,并为所有子类创建正确的实现。这是合适的,因为我们需要将这个@PostLoad应用于JPA,因为基本列表被Hibernate取代。

另一种方法是不对传递给持久性实现的实现做出任何假设,并将对象转换/复制到适当的类型。这看起来更好,因为Domain对象保持简单,并且所有存储复杂性都在DAO中。在这种情况下,我们将使用IPerson和IAddress接口的Default *实现。

即使我更喜欢第二种选择,但我不一定对这种情况感到满意。任何人都可以提供任何见解或建议吗?

3 个答案:

答案 0 :(得分:4)

  

另一种方法是不对传递给持久性实现的实现做出任何假设,并将对象转换/复制到适当的类型。这看起来更好,因为Domain对象保持简单,并且所有存储复杂性都在DAO中。另一种方法是不对传递到持久性实现的实现进行任何假设,并将对象转换/复制到适当的类型

这是我在Spring MVC中遵循的模式

  • 一个域对象包,它没有引用服务/ DAO代码(将其视为您的模型)
  • 控制器层,服务层和DAO层在域对象上运行
  • 要处理表单控制器,请使用单独的“命令”或“表单”对象层,这些对象模拟用户在表单中填写的数据,您的域对象。用户提交给控制器,该控制器将请求绑定到“命令”/“表单”对象,并且您的控制器将这些bean映射或转换为您的域bean。

例如,您可能有一个非常丰富的User对象,但是当新用户注册时,他们只需要提供2或3个字段。我将其建模为UserSignupCommandUserSignupController将其用作命令类(不是User域对象)。然后,控制器负责获取UserSignupCommand bean并将数据转换为User bean或服务层所需的任何其他类型的输入。

我不建议使用域对象作为表单支持对象,因为在大多数情况下,“我正在建模的域中的对象”和“用户在表单中提供的数据”之间没有真正的匹配。 / p>

答案 1 :(得分:4)

让多个类集合体现相同的业务数据只是有不同的调整是非常讨厌的。真的很讨厌,如果我不得不在诸如放弃你的一种持久方式之间做出选择,比如JAXB,并且有多个实现,我宁愿放弃技术并找到更好的方法去做,因为拥有所有那些无意识的代码是一个重大的痛苦。

Alan Perlis说:“在一个数据结构上运行100个函数比在10个数据结构上运行10个函数更好。”您尤其希望避免在很大程度上相互重复的数据结构。维护冗余代码库并不好玩。

我建议您查看域驱动设计。这个想法是你有域对象,不知道它们是如何存储的(这称为持久性无知)。这样你的域对象可以是POJO,你通常不需要在它们上放置接口,因为除了其他域对象之外,它们不依赖于应用程序中的任何东西。 (可能存在边缘情况,它变得有用,但它不是正常情况。)您可以在域对象中放置业务逻辑(有关域对象如何与其他域对象相关的信息),而无需担心如何测试它因为没有与数据库的联系。域对象也可以在整个应用程序中自由传递,因为它们对特定层没有任何依赖关系。在依赖关系方面,域层负责应用程序的所有其他部分,但与它们无关。

所有这一切的结果是域逻辑和如何存储对象的技术细节成为单独的问题,因此每个都可以单独测试。此外,您还拥有一个中央数据结构,只能在一个地方保存所有业务数据,因此您无需进行多项更改即可进行更改。

http://www.infoq.com上有一个关于域驱动设计的免费网页。

答案 2 :(得分:1)

我通常没有像Person和Address这样的模型对象的接口。我想知道你为什么决定这么做?

我想不出每个的多个实现。如果你的IPerson有像母亲,姐妹,朋友等的实现,我建议改为使用基于角色的设计。仅仅因为“Jean is a Mother”这句话就如此顺利地滚动,并不意味着你的设计基于IS-A和继承是明智的。

当你在国家之间移动时,地址可能是一个可变的东西,所以这可能更合理,但是你很难想出一个适合美国和日本的单一界面。

  

在迭代2中,我需要支持多种存储类型,

你能澄清一下吗? “多种存储类型”是什么意思?使用关系数据库,文件系统,对象数据库等的DAO?

  

所以我为person创建了一个界面,它有多个实现,

我没有看到之前的要求如何使其成为必要。

  

和DAO持久性的接口,同样具有多个实现。

这是常见的春季习语。

我要说网络和人员层不应该担心持久性的复杂性。这就是持久性接口应该隐藏的内容。任何使其泄漏到其他层的事情都会使其客户受到损害。我想我更喜欢你的第二种选择。

但我真正的建议是消除IPerson界面,除非你能提供保密的原因。