Decoupling backing store with Spring Web MVC

2019-06-24 20:50发布

问题:

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.

Iteration 1 of my app I created a JPA mapped Person object with various attributes attached. Using the DAO pattern I created a persistence object which can getAll, store and delete people from the database. 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. 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. 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. The persistence implementation is injected by Spring. And, because that persistence object has a factory method I am all good for creating the IPerson implementation. 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.

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.

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. In this case we'd use a Default* implementation of the IPerson and IAddress interfaces.

Even though I like the second option better, I am not necessarily comfortable with this situation. Can anyone offer any insights or advice?

回答1:

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.

This is the pattern I've followed with Spring MVC

  • A package of domain objects, which have no references to services/DAO code (think of this as your model)
  • The controller layer, services layer, and DAO layer operate on Domain objects
  • 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.

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. I would model this as a UserSignupCommand and the UserSignupController uses this as it's command class (not the User domain object). 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.

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".



回答2:

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.

Alan Perlis said, "It is better to have 100 functions operate on one data structure than 10 functions on 10 data structures." 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. 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. (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.



回答3:

I usually don't have interfaces on model objects like Person and 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. 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.

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,

Can you clarify this? What does "multiple storage types" mean? DAOs that use relational datbaases, file system, object databases, etc.?

so I create an interface for person, which has multiple implementations,

I don't see how the previous requirement makes this necessary.

and an interface for the DAO persistence, again with multiple implementations.

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.