简体   繁体   English

将后备存储与Spring Web MVC分离

[英]Decoupling backing store with Spring Web MVC

This is a design pattern question, so I'll illustrate it with a simple example of an address book app. 这是一个设计模式问题,所以我将通过一个地址簿应用程序的简单示例来说明它。

Firstly a few assumptions. 首先是一些假设。 1. It appears to be acceptable to directly use DB domain objects as the backing store for Spring MVC forms. 1.直接使用DB域对象作为Spring MVC表单的后备存储似乎是可以接受的。

Iteration 1 of my app I created a JPA mapped Person object with various attributes attached. 我的应用程序的迭代1我创建了一个附加了各种属性的JPA映射Person对象。 Using the DAO pattern I created a persistence object which can getAll, store and delete people from the database. 使用DAO模式,我创建了一个持久性对象,可以从数据库中获取,存储和删除人员。 In addition I have a factory method, create, so I can get a person object. 另外我有一个工厂方法,创建,所以我可以得到一个人对象。 Using this DAO object I create a simple web front end. 使用这个DAO对象,我创建了一个简单的Web前端。 All is good. 一切都很好。

In iteration 2 I need to support multiple storage types, so I create an interface for person, which has multiple implementations, and an interface for the DAO persistence, again with multiple implementations. 在迭代2中,我需要支持多种存储类型,因此我为person创建了一个接口,它具有多个实现,以及DAO持久性的接口,同样具有多个实现。 Also, person was extended to be able to have multiple addresses. 此外,人们被扩展为能够拥有多个地址。

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

But, when it comes to updating the web interface to be able to deal with these multiple implementations I have an issue. 但是,当涉及到更新Web界面以便能够处理这些多个实现时,我遇到了问题。 The persistence implementation is injected by Spring. 持久性实现由Spring注入。 And, because that persistence object has a factory method I am all good for creating the IPerson implementation. 并且,因为持久性对象具有工厂方法,所以我非常适合创建IPerson实现。 But, if I want to do something fancy like allow multiple addresses be submitted as part of the one request then I have an issue. 但是,如果我想做一些奇特的事情,比如允许多个地址作为一个请求的一部分提交,那么我就有问题了。 To allow this to work with Spring you seem to need to use an AutoPopulatingList, so spring can just .get(#) the record an copy the attributes in. 为了使它能够与Spring一起工作,你似乎需要使用一个AutoPopulatingList,所以spring只需要.get(#)记录一个副本属性。

So, one solution to making this work is to require all persistence implementations use an autopopulating list, and create the correct implementation for all child classes. 因此,使这项工作的一个解决方案是要求所有持久性实现使用自动填充列表,并为所有子类创建正确的实现。 Is this appropriate, given that we'd need to apply this @PostLoad with JPA as the base lists get replaced by Hibernate. 这是合适的,因为我们需要将这个@PostLoad应用于JPA,因为基本列表被Hibernate取代。

The alternative is to not make any assumptions about the implementation passed into the persistence implementation and convert/copy the objects across to the appropriate type. 另一种方法是不对传递给持久性实现的实现做出任何假设,并将对象转换/复制到适当的类型。 This looks better, as then the Domain object are kept simple, and all the storage complexity is in the DAO. 这看起来更好,因为Domain对象保持简单,并且所有存储复杂性都在DAO中。 In this case we'd use a Default* implementation of the IPerson and IAddress interfaces. 在这种情况下,我们将使用IPerson和IAddress接口的Default *实现。

Even though I like the second option better, I am not necessarily comfortable with this situation. 虽然我更喜欢第二种选择,但我不一定对这种情况感到满意。 Can anyone offer any insights or advice? 任何人都可以提供任何见解或建议吗?

The alternative is to not make any assumptions about the implementation passed into the persistence implementation and convert/copy the objects across to the appropriate type. 另一种方法是不对传递给持久性实现的实现做出任何假设,并将对象转换/复制到适当的类型。 This looks better, as then the Domain object are kept simple, and all the storage complexity is in the DAO.he alternative is to not make any assumptions about the implementation passed into the persistence implementation and convert/copy the objects across to the appropriate type. 这看起来更好,因为Domain对象保持简单,并且所有存储复杂性都在DAO中。另一种方法是不对传递到持久性实现的实现进行任何假设,并将对象转换/复制到适当的类型。

This is the pattern I've followed with Spring MVC 这是我在Spring MVC中遵循的模式

  • A package of domain objects, which have no references to services/DAO code (think of this as your model) 一个域对象包,它没有引用服务/ DAO代码(将其视为您的模型)
  • The controller layer, services layer, and DAO layer operate on Domain objects 控制器层,服务层和DAO层在Domain对象上运行
  • To handle form controllers, use a separate layer of "command" or "form" objects which model the data that the user is filling out in the form, not your domain objects. 要处理表单控制器,请使用单独的“命令”或“表单”对象层,这些对象模拟用户在表单中填写的数据,而不是域对象。 Users submit to a controller which binds the request to a "command"/"form" object, and your controller maps or converts these beans to your domain beans. 用户提交给控制器,该控制器将请求绑定到“命令”/“表单”对象,并且您的控制器将这些bean映射或转换为您的域bean。

For example, you might have a pretty rich User object, but when new Users sign up, they only need to supply 2 or 3 fields. 例如,您可能拥有非常丰富的User对象,但是当新用户注册时,他们只需要提供2或3个字段。 I would model this as a UserSignupCommand and the UserSignupController uses this as it's command class (not the User domain object). 我将其建模为UserSignupCommandUserSignupController使用它作为命令类(不是User域对象)。 The controller is then responsible for taking the UserSignupCommand bean and either converting the data to a User bean or whatever other type of input your services layer requires. 然后,控制器负责获取UserSignupCommand bean并将数据转换为User bean或服务层所需的任何其他类型的输入。

I would not recommend using domain objects as the form backing objects because in most cases there is not a true matchup between "the object in my domain I am modeling" and "the data supplied by the user in a form". 我不建议使用域对象作为表单支持对象,因为在大多数情况下,“我正在建模的域中的对象”和“用户在表单中提供的数据”之间没有真正的匹配。

It is really nasty to have multiple sets of classes that embody the same business data just with different tweakage. 拥有多组类,只需要进行不同的调整即可体现相同的业务数据,这真是令人讨厌。 Really nasty as in if I had to choose between something like ditching one of your ways of persisting, like JAXB, and having the multiple implentations, I would rather ditch the technology and find a better way to do it, because having all that mindless code is a major pain. 真的很讨厌,如果我不得不在诸如抛弃你的一种持久方式之类的东西之间做出选择,比如JAXB,并且有多个实现,我宁愿放弃技术并找到更好的方法去做,因为拥有所有那些无意识的代码是一个重大的痛苦。

Alan Perlis said, "It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures." Alan Perlis说:“在一个数据结构上运行100个函数比在10个数据结构上运行10个函数更好。” You especially want to avoid data structures that largely duplicate each other. 您尤其希望避免在很大程度上相互重复的数据结构。 Maintaining a redundant code base is no fun. 维护冗余代码库并不好玩。

I suggest you check out Domain-Driven Design. 我建议你看一下Domain-Driven Design。 The idea is you have domain objects that do not have any knowledge of how they are stored (this is called Persistence Ignorance). 这个想法是你有域对象,不知道它们是如何存储的(这称为持久性无知)。 That way your domain objects can be POJOs, you usually don't need to put interfaces on them because they have no dependencies on anything in your application except for other domain objects. 这样你的域对象可以是POJO,你通常不需要在它们上放置接口,因为除了其他域对象之外,它们不依赖于应用程序中的任何东西。 (There might be edge cases where it becomes useful but it would not be the normal case.) You can put your business logic (information about how your domain object relates to other domain objects) in the domain object without worrying about how to test it because there's no tie to the database. (可能存在边缘情况,它变得有用,但它不是正常情况。)您可以在域对象中放置业务逻辑(有关域对象如何与其他域对象相关的信息),而无需担心如何测试它因为没有与数据库的联系。 Also the domain objects can be passed around freely throughout your application because they don't have any dependencies on a specific layer. 域对象也可以在整个应用程序中自由传递,因为它们对特定层没有任何依赖关系。 In terms of dependencies the domain layer is responsible to all the other parts of the app, but independent of them. 在依赖关系方面,域层负责应用程序的所有其他部分,但与它们无关。

The upshot of all this is that domain logic and technical details of how objects are stored become separate concerns so each can be tested separately. 所有这一切的结果是域逻辑和如何存储对象的技术细节成为单独的问题,因此每个都可以单独测试。 Also you have a central data structure that holds all the business data in only one place so you don't have multiple changes to make when something changes. 此外,您还拥有一个中央数据结构,只能在一个地方保存所有业务数据,因此您无需进行多项更改即可进行更改。

There is a free web book available on Domain-Driven Design from http://www.infoq.com . 有关域驱动设计的免费网页,可从http://www.infoq.com获取

I usually don't have interfaces on model objects like Person and Address. 我通常没有像Person和Address这样的模型对象的接口。 I wonder why you've decided to go that way? 我想知道你为什么决定这么做?

I can't think of multiple implementations for each. 我想不出每个的多个实现。 If your IPerson has implementations like Mother, Sister, Friend, etc., I'd recommend going with a role-based design instead. 如果你的IPerson有像母亲,姐妹,朋友等的实现,我建议改为使用基于角色的设计。 Just because the phrase "Jean is a Mother" rolls off the tongue so nicely doesn't mean that you are wise to base your design on IS-A and inheritance. 仅仅因为“Jean是一位母亲”这句话就如此顺利地滚出舌头并不意味着你的设计基于IS-A和继承是明智的。

Address can be a variable thing as you move between countries, so that might be more reasonable, but you'll have a very hard time coming up with a single interface that will suit for both the US and Japan. 当你在国家之间移动时,地址可能是一个可变的东西,所以这可能更合理,但是你很难想出一个适合美国和日本的单一界面。

In iteration 2 I need to support multiple storage types, 在迭代2中,我需要支持多种存储类型,

Can you clarify this? 你能澄清一下吗? What does "multiple storage types" mean? “多种存储类型”是什么意思? DAOs that use relational datbaases, file system, object databases, etc.? 使用关系数据库,文件系统,对象数据库等的DAO?

so I create an interface for person, which has multiple implementations, 所以我为person创建了一个接口,它有多个实现,

I don't see how the previous requirement makes this necessary. 我不明白以前的要求是如何做到这一点的。

and an interface for the DAO persistence, again with multiple implementations. 和DAO持久性的接口,同样具有多个实现。

This is the usual Spring idiom. 这是通常的春天成语。

I'd say that the web and person tiers should not have to worry about the persistence complexity. 我要说网络和人员层不应该担心持久性的复杂性。 That's what the persistence interface is supposed to be hiding. 这就是持久性接口应该隐藏的内容。 Anything that makes it leak out into the other layers is doing its clients a disservice. 任何使其泄漏到其他层的事情都会使其客户受到损害。 I think I'd prefer your second option. 我想我更喜欢你的第二种选择。

But my real recommendation would be to eliminate the IPerson interface unless you can provide an air tight reason for keeping it. 但我真正的建议是消除IPerson接口,除非你能提供保密的原因。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM