This is the first part of a (short) series of blogs on implementing a rich domain model using only immutable domain objects. This first part introduces a (simple) example domain model and provides a JPA implementation using Scala. This exampe will serve as a baseline that should be familiar to most developers who have experience with an ORM. The other parts of this series will redesign the example to use only immutable objects and will explore some of the benefits and drawbacks of doing so.
So why even try to achieve immutability in your domain classes? What makes immutable objects useful compared to mutable ones?
Furthermore the rising availability (and popularity?) of functional programming languages (such as Clojure, F#, Haskell, and Scala) make it easier to work with immutable data than imperative languages such as Java. This can have important implications on how we design business applications, so it is worth exploring making the domain model immutable, especially since a business domain model is often considered to be inherently mutable.
The example is a (simplified) version of an Invoice
. Invoices consist of a recipient, invoice items, and the invoice amount. The lifecycle of our simplified invoice consists of the following stages:
The diagram to the right depicts the main interface of our Invoice class. The methods are grouped in relation to the state of the invoice.
The full JPA implementation is available as Invoice.scala on github. Test cases are also available. Below you’ll find a small excerpt from the Invoice
implementation:
@OneToMany(cascade = Array(CascadeType.ALL))
@OrderBy
private var _items: List[InvoiceItem] = new ArrayList
@Basic(optional = false)
private var _totalAmount: BigDecimal = BigDecimal.ZERO
@Temporal(TemporalType.DATE)
private var _sentDate: Date = _
def sent_? = _sentDate != null
def removeItem(index: Int) {
require(!sent_?, "items cannot be changed after invoice is sent")
val item = _items.remove(index)
_totalAmount = _totalAmount.subtract(item.amount)
}
Even for such a simplified example the resulting code is already quite large and feels messy. The mix of low-level data manipulation (JPA annotations, conversions between Joda-Time dates and the Java dates supported by JPA, use of null
instead of Option
s) with the high-level behavioral code seems to be a clear violation of the Single Responsibility Principle (SRP).
This is because we are trying to meet three separate needs that are common to many business applications inside a single class:
The JPA implementation of the example makes various compromises with regards to these three needs:
Besides being messy, there is also something strange about hiding the data of an Invoice behind a behavioral interface, but then fully exposing this data to the ORM, as well as any queries and reports that access this information. Furthermore “getters” are provided so the data can be used by clients. Encapsulation should make it possible to easily change the implementation without any of the user of an Invoice
being aware of this, but anyone who has tried to actually do this with JPA backed objects knows it is not as easy as it should be!
Is it possible to split this object into three different ones, each with just a single responsibility? Certainly, and many existing systems already do so. The Invoice
entity can be implemented as an anemic domain object, and is used to provide the required durability. The behavioral aspects are implemented by controller, manager, or service objects, and the reporting needs are provided by a DAO. This often results in procedural transaction scripts that implement the behavioral requirements by mutating the “durable” entity. Although we could redesign our Invoice this way, it only seems to lead us further from an immutable domain model.
In the next part we’ll look at a different way meet these three basic needs which does allow an immutable implementation of the domain model. The name of the game will be the addictive combination of CQRS and Event Sourcing.