View Models
Introduction
View models are similar to entities in that (unlike domain services) there can be many instances of any given type. End users interact with view models in the same way as a domain entity, indeed they are unlikely to distinguish one from the other.
However, whereas domain entities are mapped to a datastore, view models are not. Instead, they are recreated dynamically by serializing their state, ultimately into the URL itself (meaning their state it is in effect implicitly managed by the client browser). You will notice that the URL for view models (as shown in Web UI (Wicket viewer) or RestfulObjects viewer) tends to be quite long.
This capability opens up a number of more advanced use cases:
-
In the same way that an (RDBMS) database view can aggregate and abstract from multiple underlying database tables, a view model sits on top of one or many underlying entities.
-
A view model could also be used as a proxy for some externally managed entity, accessed over a web service or REST API; it could even be a representation of state held in-memory (such as user preferences, for example).
-
view models can also be used to support a particular use case. An example that comes to mind is to expose a list of scanned PDFs to be processed as an "intray", showing the list of PDFs on one side of the page, and the current PDF being viewed on the other. Such view models are part of the application layer, not part of the domain layer (where entities live).
We explore these use cases in more detail below.
Application-layer view models
Domain entities (whether locally persisted or managed externally) are the bread-and-butter of Apache Causeway applications: the focus after all, should be on the business domain concepts and ensuring that they are solid. Generally those domain entities will make sense to the business domain experts: they form the ubiquitous language of the domain. These domain entities are part of the domain layer.
When developing an Apache Causeway application you will most likely start off with the persistent domain entities: Customer
, Order
, Product
, and so on.
For some applications this may well suffice.
That said, it may not always be practical to expect end-users of the application to interact solely with those domain entities. If the application needs to integrate with other systems, or if the application needs to support reasonably complex business processes, then you may need to look beyond just domain entities; view models are the tool of choice.
One such use case for view models is to help co-ordinate complex business processes; for example to perform a quarterly invoicing run, or to upload annual interest rates from an Excel spreadsheet, or prepare payment batches from incoming invoices, to be uploaded to an external payment system. In these cases the view model managing the business process might have some state of its own, but in most cases that state does not need to be persisted between user sessions. Many of the actions will be queries but in some cases such view model actions might also modify state of underlying domain entities. Either way, ultimately these actions just delegate down to the domain-layer.
Another common requirement is to show a dashboard of the most significant data in the system to a user, often pulling in and aggregating information from multiple points of the app. Obtaining this information by hand (by querying the respective services/repositories) would be tedious and slow; far better to have a dashboard do the job for the end user.
A dashboard object is a model of the most relevant state to the end-user, in other words it is (quite literally) a view model. It is not a persisted entity, instead it belongs to the application layer.
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. Because such entities are responsible for their own state management, they can be implemented as view models.
Externally-managed entities
Sometimes the entities that make up your application are persisted not in the local database but reside in some other system, for example accessible only through a SOAP web service. Logically that data might still be considered a domain entity and we might want to associate behaviour with it, however it cannot be modelled as a domain entity if only because JDO/DataNucleus doesn’t know about the entity nor how to retrieve or update it.
There are a couple of ways around this: we could either replicate the data somehow from the external system into the Causeway-managed database (in which case it is once again just another domain entity), or we could set up a stub/proxy for the externally managed entity. This proxy would hold the reference to the externally-managed domain entity (eg an external id), as well as the "smarts" to know how to interact with that entity (by making SOAP web service calls etc).
The stub/proxy is a type of view model: a view — if you like — onto the domain entity managed by the external system.
Internal entities
Alternatively, perhaps your application has an entity that is best persisted not in a relational database but instead in a custom datastore, for example a graph database such as neo4j.
In-memory entities
As a variation on the above, sometimes there are domain objects that are, conceptually at least entities, but whose state is not actually persisted anywhere, merely held in-memory (eg in a hash).
A simple example is read-only configuration data that is read from a config file (eg log4j appender definitions) but thereafter is presented in the UI just like any other entity.
DTOs
DTOs (data transfer objects) are simple classes that (according to wikipedia) "carry data between processes".
If those two processes are parts of the same overall application (the same team builds and deploys both server and client) then there’s generally no need to define a DTO; just access the entities using Apache Causeway' RestfulObjects viewer.
On the other hand, if the client consuming the DTO is a different application — by which we mean developed/deployed by a different (possible third-party) team — then the DTOs act as a formal contract between the provider and the consumer. In such cases, exposing domain entities over RestfulObjects would be "A Bad Thing"™ because the consumer would in effect have access to implementation details that could then not be easily changed by the producer. There’s plenty of discussion on this topic (eg here and here). Almost all of these recommend exposing only DTOs (which is to say view models), not domain entities, in REST APIs.
To support this use case, a view model can be defined such that it can act as a DTO. This is done by annotating the class using JAXB annotations; this allows the consumer to obtain the DTO in XML format along with a corresponding XSD schema describing the structure of that XML.
These DTOs are still usable as "regular" view models; they will render in the Web UI (Wicket viewer) just like any other. In fact (as the programming model section below makes clear), these JAXB-annotated view models are in many regards the most powerful of all the alternative ways of writing view models.
For REST Clients
The Restful Objects viewer automatically provides a REST API for both domain entities. Or, you can use it to only expose view models, taking care to map the state of the domain entity/ies into a view model. The question to consider is whether the REST API is a public API or an internal private API:
-
If it’s a public API, which is to say that there are third-party clients out over which you have no control, then view models are the way to go.
In this case view models provide an isolation layer which allow you to modify the structure of the underlying domain entities without breaking this API.
-
If it’s a private API, which is to say that the only clients of the REST API are under your control, then view models are an unnecessary overhead.
In this case, just expose domain entities directly.
The caveat to the "private API" option is that private APIs have a habit of becoming public APIs. Even if the REST API is only exposed within your organisation’s intranet, other teams may "discover" your REST API and start writing applications that consume it. If that REST API is exposing domain entities, you could easily break those other teams' clients if you refactor.
The Spring Data REST subproject has a similar capability of being able to expose domain entities as REST resources. This SO question, which debates the pros-and-cons, is also worth a read. |
If your REST API is intended to be public (or you can’t be sure that it will remain private), then exposing view models will entail a lot of marshalling of state from domain entities into view models. There are numerous open source tools that can help with that, for example Model Mapper, Dozer and Orika.
Or, rather than marshalling state, the view model could hold a reference to the underlying domain entity/ies and dynamically read from it (ie, all the view model’s properties are derived from the entity’s).
A third option is to define an RDBMS view, and then map a "non-durable" entity to that view. The RDBMS view then becomes the public API that must be preserved. ORMs such as DataNucleus support this.
Programming Model
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.
View models are typically annotated with @DomainObject(nature=VIEW_MODEL).
The framework provides four different 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.
-
Implementing
java.io.Serializable
.View models implemented this way cannot reference entities, either as properties or collections, and indeed must make sure that every other object referenced is also
Serializable
.Domain services can be injected, but must be annotated declared with the
transient
keyword.
If a view model class employs several of these options, then there is a precedence:
-
explicitly implemented interfaces take precedence:
ViewModel
interface overSerializable
-
JAXB-annotated classes are next
-
Causeway-annotated classes are the fallback.
Each of these options are discussed in more detail in the sections below.
JAXB View Models
The most powerful way to implement a view model (at the expense of some boilerplate) is to use JAXB view models. These can serialize both properties (value types and references to entities) and collections (references to entities).
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. |
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 simply rely on the fact that the view model is annotated with @DomainObject(nature=VIEW_MODEL).
With this approach the state of properties — but not collections — is serialized. So this approach is not as powerful as using the JAXB-style of view models, on the other hand it takes little 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. |
Of all the alternative ways to implement a view model, this is probably the simplest. However, it also has the most limitations. As noted above, it can only capture the values of properties, not collections. Moreover, those property types must be built-in value types or to entities. It is not possible though to reference another view model instance.
ViewModel
interface
The most flexible approach to implement a view model is to implement the ViewModel interface.
This defines a single method, viewModelMemento()
, which returns a String representation of the object’s state.
Or, it could return be a handle to its state if the state is actually stored elsewhere, eg in an external database.
The corollary is that the view model must also define a constructor whose first parameter is a String
.
This constructor can optionally take additional injected services, which the view model can use to initialize itself (eg look up from the external database, if required).
For example:
@DomainObject
public class CompareCustomers implements ViewModel { (1)
public CompareCustomers(
String memento, (2)
CustomerRepository customerRepository (3)
) {
customer1 = customerRepository.findByRef(memento.split(":")[0]); (4)
customer2 = customerRepository.findByRef(memento.split(":")[1]); (3)
}
public String viewModelMemento() {
return getCustomer1().getRef() + ":" + getCustomer2().getRef(); (5)
}
@Getter @Setter
Customer customer1;
@Getter @Setter
Customer customer2;
public String getCustomer1Name() {
return getCustomer1().getName();
}
public String getCustomer2Name() {
return getCustomer2().getName();
}
...
}
1 | View model’s state as originally returned by viewModelMemento() (below); mandatory |
2 | Injected domain services; optional |
3 | Unpacks the memento and uses it to restore state |
4 | Encodes the current state into a string |
Serializable View Models
A further another option for view models is for the class to implement java.io.Serializable
.
This is a lower-level abstraction: you must take care that all fields referenced by the view model are serializable.
Most of the value types are indeed serializable (including date/time classes), but for reference types, you will need to store the reference as a bookmark.
You can still inject services, but the fields these must be transient
(that is, using the Java keyword).
For example:
@DomainObject
public class CompareCustomers implements java.io.Serializable { (1)
@Inject transient BookmarkService bookmarkService; (2)
Bookmark customer1Bookmark;
Bookmark customer2Bookmark;
public Customer getCustomer1{} {
return bookmarkService.lookup(customer1Bookmark, Customer.class).orElse(null);
}
public Customer getCustomer2{} {
return bookmarkService.lookup(customer1Bookmark, Customer.class).orElse(null);
}
...
}
1 | View model class implements java.io.Serializable |
2 | Injected domain services must be transient |
UrlEncodingService
When implementing view models, you must 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).
Another reason to provide your own implementation is security: perhaps to encrypt the serialization of the content with a key specific to the currently logged in user. This would prevent your end-users from sharing view model URLs, potentially leaking information.
A custom implementation might also encode a time period within which the URL can be used before becoming invalid.