Dto
Allows JAXB-annotated view models to act as a mixee in order that other modules (and the core framework) can contribute behaviour.
A JAXB view model is one annotated with jakarta.xml.bind.annotation.XmlRootElement .
The two mixin behaviours contributed by the core framework are the ability to download the view model as XML (using Dto_downloadXml ) and to download the XSD schema for that XML (using Dto_downloadXsd ).
The interface is just a marker interface (with no members).
Example
The examples in this section uses the DTO for a ToDoItem
entity.
The DTO is defined as follows:
package todoapp.app.viewmodels.todoitem.v1; (1)
@XmlRootElement(name = "toDoItemDto") (2)
@XmlType(
propOrder = { (3)
"majorVersion", "minorVersion",
"description", "category", ...
"toDoItem", "similarItems"
}
)
@DomainObjectLayout(
titleUiEvent = TitleUiEvent.Doop.class (4)
)
public class ToDoItemV1_1 implements Dto { (5)
@XmlElement(required = true, defaultValue = "1") (6)
public final String getMajorVersion() { return "1"; }
@XmlElement(required = true, defaultValue = "1") (7)
public String getMinorVersion() { return "1"; }
@XmlElement(required = true) (8)
@Getter @Setter
protected String description;
@XmlElement(required = true)
@Getter @Setter
protected String category;
...
@Getter @Setter (9)
protected ToDoItem toDoItem;
@XmlElementWrapper (10)
@XmlElement(name = "todoItem")
@Getter @Setter
protected List<ToDoItem> similarItems = Lists.newArrayList();
}
1 | package name encodes major version, so it can be serialized; see discussion on versioning below |
2 | identifies this class as a view model and defines the root element for JAXB serialization |
3 | all properties in the class must be listed; (they can be ignored using @XmlTransient ) |
4 | demonstrating use of UI events for a subscriber to provide the DTO’s title; see @DomainObjectLayout#titleUiEvent(). |
5 | class name encodes (major and) minor version; see discussion on versioning |
6 | again, see discussion on versioning |
7 | again, see discussion on versioning |
8 | simple scalar properties |
9 | reference to a persistent entity; discussed here |
10 | reference to a collection of persistent entities; again discussed here |
Versioning
The whole point of using DTOs (in Apache Causeway, at least) is to define a formal contact between two inter-operating but independent applications. Since the only thing we can predict about the future with any certainty is that one or both of these applications will change, we should version DTOs from the get-go. This allows us to make changes going forward without unnecessarily breaking existing consumers of the data.
There are several ways that versioning might be accomplished; we base our guidelines on this article taken from Roger Costello’s blog, well worth a read. |
We can distinguish two types of changes:
-
backwardly compatible changes
-
breaking changes.
The XSD namespace should change only when there is a major/breaking change. If following semantic versioning that means when we bump the major version number v1, v2, etc.
XML namespaces correspond (when using JAXB) to Java packages.
We should therefore place our DTOs in a package that contains only the major number; this package will eventually contain a range of DTOs that are intended to be backwardly compatible with one another.
The package should also have a package-info.java
; it is this that declares the XSD namespace:
@javax.xml.bind.annotation.XmlSchema(
namespace = "http://viewmodels.app.todoapp/todoitem/v1/Dto.xsd", (1)
xmlns = {
@javax.xml.bind.annotation.XmlNs(
namespaceURI = "https://causeway.apache.org/schema/common",
prefix = "com"
)
},
elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED
)
package todoapp.app.viewmodels.todoitem.v1; (2)
1 | the namespace URI, used by the DTO residing in this package. |
2 | the package in which the DTO resides. Note that this contains only the major version. |
Although there is no requirement for the namespace URI to correspond to a physical URL, it should be unique. This usually means including a company domain name within the string.
As noted above, this package will contain multiple DTO classes all with the same namespace; these represent a set of minor versions of the DTO, each subsequent one intended to be backwardly compatible with the previous. Since these DTO classes will all be in the same package (as per the advice above), the class should therefore include the minor version name:
package todoapp.app.viewmodels.todoitem.v1; (1)
...
public class ToDoItemV1_1 implements Dto { (2)
...
}
1 | package contains the major version only |
2 | DTO class contains the (major and) minor version |
We also recommend that each DTO instance should also specify the version of the XSD schema that it is logically compatible with. Probably most consumers will not persist the DTOs; they will be processed and then discarded. However, it would be wrong to assume that is the case in all cases; some consumers might choose to persist the DTO (eg for replay at some later state).
Thus:
public class ToDoItemV1_1 implements Dto {
@XmlElement(required = true, defaultValue = "1")
public final String getMajorVersion() { return "1"; } (1)
@XmlElement(required = true, defaultValue = "1")
public String getMinorVersion() { return "1"; } (2)
...
}
1 | returns the major version (in sync with the package) |
2 | returns the minor version (in sync with the class name) |
These methods always return a hard-coded literal. Any instances serialized from these classes will implicitly "declare" the (major and) minor version of the schema with which they are compatible. If a consumer has a minimum version that it requires, it can therefore inspect the XML instance itself to determine if it is able to consume said XML.
If a new (minor) version of a DTO is required, then we recommend copying-and-pasting the previous version, eg:
public class ToDoItemV1_2 implements Dto {
@XmlElement(required = true, defaultValue = "1")
public final String getMajorVersion() { return "1"; }
@XmlElement(required = true, defaultValue = "2")
public String getMinorVersion() { return "2"; }
...
}
Obviously, only changes made must be backward compatible, eg new members must be optional.
Alternatively, you might also consider simply editing the source file, ie renaming the class and bumping up the value returned by getMinorVersion()
.
DTO Consumers
The actual consumers of DTOs will generally obtain the XML of the view models either by requesting the XML directly, eg using the RestfulObjects viewer, or may have the XML sent to them asynchronously using an ESB such as Apache Camel.
In the former case, the consumer requests the DTO by calling the REST API with the appropriate HTTP Accept
header.
An appropriate implementation of ContentMappingService can then be used to return the appropriate DTO (as XML).
For the latter case, one design is simply for the application to instantiate the view model, then call the JaxbService to obtain its corresponding XML. This can then be published onto the ESB, for example using an Apache ActiveMQ ™ queue.
However, rather than try to push all the data that might be needed by any of these external systems in a single XML event (which would require anticipating all the requirements, likely a hopeless task), a better design is to publish only the fact that something of note has changed - ie, that an action on a domain object has been invoked - and then let the consumers call back to obtain other information if required.
This can once again be done by calling the REST API with an appropriate HTTP Accept
header.
This is an example of the VETRO pattern (validate, enrich, transform, route, operate). In our case we focus on the validation (to determine the nature of the inbound message, ie which action was invoked), and the enrich (callback to obtain a DTO with additional information required by the consumer). |