I'm in the process of converting a webapp to using struts, am trying to use "best" practices to make sure that I keep presentation split from business, and I was wondering what all of you experts do to handle the data transition from model business logic beans to the presentation.
But as long as you are presenting properties that have already been transferred from the data storage to working memory, just do it. =:0)
The presentation page uses reflection, so you are not coupling your page to the model bean type but to its "protocol" -- what it names the properties. If the model bean properties ever change, you can always substitute an adapter, or "wrapper" bean, that maps the new properties to the old properties. (Or just update the pages, whichever is preferable.)
Gathering input from the user is a thornier problem.
(1) The first issue is that ActionForms should only use String and boolean properties. Why? Because the primary job of an ActionForm is to allow for validating input. A non-String property can't store whatever invalid input a client might present. We could just throw it away, but the classic Struts use case has always been that we should present invalid input for correction, exactly as it was input.
There is nothing to prevent you from using an Integer for an ActionForm property, but if the user inputs "12ZY4" you can't re-present that value for correction.
<ot> Though, I've been wondering why we just don't have the tags (or whatever) check the request if a property is null, and use the request for the input buffer instead. (I might try this in Velocity.) There has also been a recent patch proposed regarding validating maps, which might also work for a HttpRequest. If we can use the request to buffer invalid data, then we can use native types. If we can use native types, then it becomes easier to implement a business interface on your ActionForm. Business methods can then interact through the interface. </ot>
One alternative here is to provide both Strings and native versions of your properties (like a ResultSet). But then you either end up replicating the String properties or creating confusion over which property to call when. If you go this way, pretty soon it becomes just as easy to maintain two separate beans with distinct purposes.
(2) The second argument is security. Populating a method on a business bean might fire a business process that we aren't ready to execute yet. Good argument, but with limited applicability. Many persistence systems are transparent and don't have methods like that.
(3) The third argument is validation. Most business beans, it is posed, do not know how to validate themselves, and they especially don't know how to cope with web semantics and web validation issues.
<ot> Though, I'm thinking validation is a perfectly reasonable thing for a persisted object to do. And being able to validate String input, in these days of multi-tier development, is also a reasonable feature now.
Any bean that might be populated from an external resource, whether it is a database, XML element, or HTTP request, should be able to validate itself. There's no telling what happened to its data between sessions. For example, a DBA could have run a query that changed its state in an unexpected way. Defensive programming would seem to dictate that a persisted bean must know how to validate (or test) itself at runtime. This is not a presentation issue, it's a model-state issue. </ot>
I'm still kinda sold on the composition argument. I find that a "data entry object" does make sense for most complex applications. Often, we need to format the data in certain ways outside of what the model would expect. We may also need to provide helper methods to make HTML controls easier to render. And we should identify the input fields, as opposed to whatever other non-input fields may exist in the model. In practice, a Data Entry Object becomes a coarse-grained, denormalized transfer object, where the property userName often equates to something like user.Name in the model. The DEO can be an interface defined in the business layer but implemented in the presentation layer.
Meanwhile, I find that my ActionForms often need properties that don't exist in my persistence model, but do exist in my data access signatures. So the Data Entry Object is a place where you can encapsulate what you need for
- persistence objects (straight data)
- data access methods (filters)
- presentation objects (formatting)
To help get from the ActionForm properties to the persistence properties, I'm starting to think about a fancy version of BeanUtils.Populate that would automatically map patterns like userName to user.Name if you passed it a set of target beans. It might also take a configuration to map coarse names to fine names, the way Hibernate maps properties to columns.
(I'm actually starting to think in terms of a master XML element that could be used to map DEOs (ActionForms) to persistence objects to relational tables, with the appropriate validations between each layer. This might be used to create separate XML documents for each gizmo you are using [Validator, Hibernate, Struts]. The convenience of a desktop database dialog with the power of native tools.)
So, for now, I generally recommend defining a coarse-grained ActionForm with all the input properties that you application uses. (For larger applications, it may be a module or other logical subset of the application.)
For each set of validations (or "forms"), define a different formbean. If you are using the Struts Validator, you don't have to define a new subclass (the validator goes by the formbean/attribute name). Otherwise, you can subclass the "properties" ActionForm and define a validate method.
This approach lowers maintenance, maximizes coherence, and minimizes coupling. Achieving high coherence and low coupling is the primary goal of a MVC architecture. There is still duplication between data-entry and model properties, but it is manageable.
The "duplication" is even *useful* for Test Driven Design. You can start by designing the application using coarse-grained ActionForms and let the UI tell you what data access signatures you need. Your object graph or database schema can then live on the other side of your data access signatures and be whatever they need to be. This gives you the chance to design the model (object/database layer) after you have tested what the client/UI actually needs.
In practice, we often design the model too early and end up writing code that we never use. A coarse-grained data entry object lets you design and test the UI before mucking about with the business object or db layer.
In the Action, you can use BeanUtil.copyProperties to beam over any properties that happen to match. For those that don't, you may have to resort to a manual transfer. [person.setName(actionForm.getPersonName)] Some people use an adaptor or data mapping object here to encapsulate the dirty work. We probably need a better general purpose utility here, like BeanUtils.mapProperties, or something.
Often, there is a relationship between the form validations and your use-case (or client story). It can be useful to name your formbean after the underlying use case, to help keep everything straight. I tend to use generic names for my ActionForm classes (like FormProperties and FormHelper), but very specific names for formbeans (like "permit_search_all" and "permit_store"). This keeps the formbeans/validations aligned with the use-cases they represent (store a permit, search all the permits).
(See also "Wither ActionMappings" <http://www.mail-archive.com/msg57843.html>)
Struts in Action