View Models
As described in the overview, view models are generally domain objects concerned with facilitating or optimising a particular business process. As such, they bring together the domain entities involved in that process and provide actions to be performed against or on those domain entities.
Such view models are generally considered to reside in the application layer, and — unlike domain entities — their state is not persisted to a database. Instead, it is serialized into its identifier (in effect, its URL). The framework unpacks this URL to infer/recreate the view model’s state with each interaction.
The framework provides three ways to implement a view model:
-
Annotating the class using JAXB annotations; this allows the state of the object’s properties and also its collections.
The serialized form of these view models is therefore XML, which also enables these view models to act as DTO (useful for various integration scenarios).
-
Using Apache Causeway specific annotations.
This is more concise, but less powerful: only the state of the object’s properties is serialized — collections are ignored — and not every datatype is recognized.
On the other hand, they are more likely to perform better.
-
Implementing the ViewModel interface.
With this option you take full control of the marshalling and unmarshalling of the object’s state to/from a string.
These options are discussed in more detail in the sections below.
JAXB View Models
Here’s a typical example of a JAXB view model, to allow (certain properties of) two Customer
s to be compared:
@XmlRootElement(name = "compareCustomers") (1)
@XmlType(
propOrder = { (2)
"customer1",
"customer2"
}
)
@XmlAccessorType(XmlAccessType.FIELD) (3)
public class CompareCustomers {
@XmlElement(required = true) (4)
@Getter @Setter
Customer customer1;
@XmlElement(required = true) (5)
@Getter @Setter
Customer customer2;
@XmlTransient (6)
public String getCustomer1Name() {
return getCustomer1().getName();
}
@XmlTransient (7)
public String getCustomer2Name() {
return getCustomer2().getName();
}
...
}
1 | The JAXB @XmlRootElement annotation indicates this is a view model to Apache Causeway, which then uses JAXB to serialize the state of the view model between interactions |
2 | Optionally, the properties of the view model can be listed using the XmlType#propOrder attribute.This is an all-or-nothing affair: either all properties must be listed, or else the annotation omitted. |
3 | Specifying field accessor type allows the Lombok @Getter and @Setter annotations to be used. |
4 | The @XmlElement indicates the property is part of the view model’s state.
For collections, the @XmlElementWrapper would also typically be used. |
5 | The @XmlTransient indicates that the property is derived and should be ignored by JAXB.The derived properties could also have been implemented using mixins. |
Be aware that all the state will ultimately converted into a URL-safe form (by way of the UrlEncodingService). There are limits to the lengths of URLs, however. If the URL does exceed limits or contains invalid characters, then provide a custom implementation of UrlEncodingService to handle the memento string in some other fashion (eg substituting it with a GUID, with the memento cached somehow on the server). |
Referencing Domain Entities
It’s quite common for view models to be "backed by" (be projections of) some underlying domain entity.
For example, the CompareCustomers
view model described above actually references two underlying Customer
entities.
It wouldn’t make sense to serialize out the state of a persistent entity.
However, the identity of the underlying entity is well defined; Apache Causeway defines the common schema which defines the <oid-dto>
element (and corresponding OidDto
class): the object’s type and its identifier.
This is basically a formal XML equivalent to the Bookmark
object obtained from the BookmarkService.
There is only one requirement to make this work: every referenced domain entity must be annotated with @XmlJavaTypeAdapter, specifying the framework-provided PersistentEntityAdapter
.
And this class is similar to the BookmarkService: it knows how to create an OidDto
from an object reference.
Thus, in our view model we can legitimately write:
public class CompareCustomers {
@XmlElement(required = true)
@Getter @Setter
Customer customer1;
...
}
All we need to do is remember to add that @XmlJavaTypeAdapter
annotation to the referenced entity:
@XmlJavaTypeAdapter(PersistentEntityAdapter.class)
public class Customer ... {
...
}
It’s also possible for a DTO view models to hold collections of objects.
These can be of any type, either simple properties, or references to other objects.
The only bit of boilerplate that is required is the @XmlElementWrapper
annotation.
This instructs JAXB to create an XML element (based on the field name) to contain each of the elements.
(If this is omitted then the contents of the collection are at the same level as the properties; almost certainly not what is required).
For example, we could perhaps generalize the view model to hold a set of Customer
s to be compared:
public class CompareCustomers {
...
@XmlElementWrapper
@XmlElement(name = "customers")
@Getter @Setter
protected List<Customer> customersToCompare = Lists.newArrayList();
}
JODA Time Datatypes
If your JAXB view model contains fields using the JODA datatypes (LocalDate
and so on), then @XmlJavaTypeAdapter
additional annotations in order to "teach" JAXB how to serialize out the state.
The Apache Causeway applib provides a number of adapters to use out-of-the-box. For example:
@XmlRootElement(name = "categorizeIncomingInvoice")
@XmlType(
propOrder = {
...
"dateReceived",
...
}
)
@XmlAccessorType(XmlAccessType.FIELD)
public class IncomingInvoiceViewModel extends IncomingOrderAndInvoiceViewModel {
@XmlJavaTypeAdapter(JodaLocalDateStringAdapter.ForJaxb.class)
private LocalDate dateReceived;
...
}
The full list of adapter classes are:
JODA datatype | Adapter |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
If you want use other Joda data types, check out this blog post. |
Non-JAXB View Models
Instead of using JAXB to specify a view model, it is also possible to use the @DomainObject with a nature of VIEW_MODEL.
This approach is not as powerful as using the JAXB-style of view models, because only the state of properties — not collections — is serialized, and moreover only certain data types are recognised. On the plus side, it takes less effort.
For example:
@DomainObject(nature = Nature.VIEW_MODEL) (1)
public class CompareCustomers {
@Property (2)
@Getter @Setter
Customer customer1;
@Property (2)
@Getter @Setter
Customer customer2;
public String getCustomer1Name() {
return getCustomer1().getName();
}
public String getCustomer2Name() {
return getCustomer2().getName();
}
...
}
1 | declares the domain object as a view model |
2 | fields must be annotated with @Property so that they are part of the metamodel.
Note that they do not need to be visible, however. |
ViewModel
interface
The most flexible approach to implement a view model is to implement the ViewModel interface.
For example:
@DomainObject
public class CompareCustomers implements ViewModel { (1)
public String viewModelMemento() { (2)
return getCustomer1().getRef() + ":"
+ getCustomer2().getRef();
}
public void viewModelInit(String memento) { (3)
val ref1 = memento.split[":"](0);
customer1 = customerRepository.findByRef(ref1));
val ref2 = memento.split[":"](1);
customer2 = customerRepository.findByRef(ref2);
}
@Getter @Setter
Customer customer1;
@Getter @Setter
Customer customer2;
public String getCustomer1Name() {
return getCustomer1().getName();
}
public String getCustomer2Name() {
return getCustomer2().getName();
}
@Inject
CustomerRepository customerRepository;
...
}
Self-persisting Domain entities
Sometimes we may have domain entities whose persistence is not managed by JDO or JPA mechanism, in other words they take responsibility for their own persistence.
We can characterise these as:
-
external entities
For example the application may interact synchronously with state exposed on another system through a REST or SOAP API. In this case the entity with your Apache Causeway application is a proxy or a facade for the state on the external system
-
internal entities
For example the entity might include a data structure that is best persisted in a custom datastore, for example a graph database such as neo4j.
Because such entities are responsible for their own state management, they can be implemented as view models.