2015-05-17

Advancing Enterprise DDD - Migrating to Immutability

In the previous essay of the Advancing Enterprise DDD series, we saw difficulties emerge in maintaining intra-aggregate constraints. These difficulties arose due to the mutability of our entities, and the Java collections they use to maintain relationships between entities. We also saw that it would be difficult or impossible to use immutable alternatives within JPA. In this essay, we set aside the constraints of JPA for a moment, and imagine a world where entities are composed of immutable objects.

As we've seen in the previous essay, Scala makes good use of immutability in its collections library, removing the need to be constantly making defensive copies of our data. In pure functional programming, all data is immutable. But Scala is not a pure functional language. It is functional, object-oriented hybrid (F/OO). Scala tries to bring in the best of both styles of programming. And while mutable data is fully supported, in general, we prefer not to use it. Code that uses immutable data is easier to reason about, and less prone to errors.

Scala's preference for mutable data can be seen in the alternative to the POJO - the case class. Both serve the same purpose: to provide a simple and easy way to describe types and objects with properties. For instance, in The POJO Myth, we saw this POJO that represents a Customer entity in the very early stages of development:

public class Customer {

    private String firstName;

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
}

The Customer has a single property, firstName. This is not exactly the same as the private field firstName in the above code, since a property must be exposed by at least a getter method. The field itself is private, so it follows the OO principle that an object's data should never be directly exposed. But in a in a sense, it may as well be public, as the getter and setter more or less directly expose it.

The Scala equivalent to the POJO above is this:

case class Customer(firstName: String)

Case classes provide a lot of great functionality that goes well beyond what Java provides with POJOs. Take one example, they come with hashCode and equals methods based on the values of the properties. Writing correct hashCode and equals methods in Java is notoriously difficult, and the subject has an extensive writeup in Joshua Bloch's book Effective Java. Typically, we rely on our IDEs to generate a nasty glob of code for us and leave it at that.

Of course, in sharp contrast to the POJO, the case class is also immutable. If we want to change the Customer's firstName, first we change the way we are thinking, and say we want a copy of the Customer with a modified firstName. And we do it like this:

val customer2 = customer1.copy(firstName = "John")

If we were to build our domain model out of case classes and immutable collections, all our problems with maintaining our intra-aggregate constraints go away. To see that, let's recall our example from the last essay: An Order has multiple Order Items, each with their own quantity and price. The Order entity itself keeps track of the total price. We end up having to recompute our total price any time an Order Item changes, as well as any time the list of Order Items changes. When everything is immutable, we only have to compute the total price once, and we can do that in our constructor. The Scala code would look something like this:

case class OrderItem(quantity: Integer, price: Amount) {
  val totalPrice = price * quantity
}

case class Order(orderItems: Seq[OrderItem]) {
  val totalPrice = orderItems.map(_.totalPrice).sum
}

So what happens when somebody tries to change one of the Order Items? The answer is they can't, it's immutable. Instead, they can make a copy of the Order Item, with one or more of its properties modified. For example, let's "change" both the quantity and price for an Order Item. First, we create an Order with two Order Items:

val order = Order(Seq(OrderItem(2, "$9.99"),
                      OrderItem(3, "$19.95")))

Now, let's grab the first Order Item:

val firstItem = order.orderItems(0)

Now, let's change it:

val modifiedItem = firstItem.copy(quantity = 4, price = "$39.95")

firstItem remains entirely unchanged, and so does the Order. If we wanted to get a new Order that was the same as the original, but with the modified Order Item, we first need to create a modified list of Order Items:

val modifiedItems = order.orderItems.updated(0, modifiedItem)

Now we can create the new Order:

val modifiedOrder = order.copy(orderItems = modifiedItems)

Migrating from mutability to immutability does take a little shift in perspective when thinking about data. But it certainly isn't any harder to understand once you make that shift. The POJO equivalent to the above modification example might take one less line of code, but in exchange, the need for defensive copies and the complexities of recomputing the total price have gone away.

Let's take a step back in this essay, and revisit the PersistentState example we developed in Rethinking the POJO - the third essay in this series. At the time, we were trying to make our POJOs look and feel more like our actual domain entities - that is, closer to what appears in our UML diagrams. As we saw in The POJO Myth, our JPA POJOs end up containing a lot of clutter that is not representative of anything in our domain model, but instead manages persistence concerns, such as database identity, optimistic locking, and diagnostic fields such as createdBy and createdDate. Here's what an equivalent case class for the JPA POJO we saw there:

case class Customer(
  customerId: Long,
  firstName: String,
  createdBy: User,
  createdDate: Date,
  lastModifiedBy: User,
  lastModifiedDate: Date,
  version: Long)

We were looking at redesigning that into something like this:

case class Customer(firstName: String)

case class PersistentState[E](
  entity: E,
  id: Long,
  firstName: String,
  createdBy: User,
  createdDate: Date,
  lastModifiedBy: User,
  lastModifiedDate: Date,
  version: Long)

So we've begun to separate concerns, as we've untangled our persistent information from our domain. But we haven't hidden any of the implementation details of the PersistentState to the user. A model like the above might be fine for working within the persistence layer, but what would we want to present to the user outside of the persistence layer? A minimalist answer would be to say that they need to be able to get and set the entity. We certainly can get and set the entity with the above code. Notice that, in the fashion of immutability, setting the entity creates a new PersistentState. Here it is, spelled out step by step:

val retrievedState: PersistentState[Customer] =
  customerRepo.retrieve(customerKey)
val retrieved: Customer =
  retrievedState.entity
val modified: Customer =
  retrieved.copy(firstName = "John")
val modifiedState: PersistentState[Customer] =
  retrievedState.copy(entity = modified)
val updatedState: PersistentState[Customer] =
  customerRepo.update(modifiedState)

There is a potential problem in the line where we create modifiedState: We've only updated the entity. Perhaps some of the persistent state information should be updated in a case like this, such as lastModifiedBy? So we really want an API that presents a getter and a setter, and hides the rest. We've already seen two similar examples that we can draw from. We saw how to create a new version of a list with the first element updated:

val modifiedItems = order.orderItems.updated(0, modifiedItem)

And we used method List.map to convert a list of Order Items to a list of total prices:

val totalPrice = orderItems.map(_.totalPrice).sum

The first example takes a modified item, and the second example takes a function that converts from Order Item to Amount. Let's try the second approach. Here is a version of PersistentState that hides all of the persistence details from the user:

trait PersistentState[E] {
  private[repo] E entity
  private[repo] Long id
  private[repo] User createdBy
  private[repo] Date createdDate
  private[repo] User lastModifiedBy
  private[repo] Date lastModifiedDate
  private[repo] Long version

  def get: E = entity

  def map(f: E => E): PersistentState[E] = {
    // return a PersistentState with updated persistence info,
    // and f(entity) as the entity
  }
}

The user doesn't have to know or understand any of the internal details. Just the API. The get method presents the entity itself to the client user, and the map method is used to modify the entity. The map method takes as argument a function f that produces a new version of the entity based on the current version of the entity. The map function applies f to the entity, updates the persistence fields as necessary, and produces a new PersistentState that reflects these changes.

To see how manipulating PersistentStates might work in practice, let's add a couple of business methods to the Customer. The first one updates some Customer preferences, and the second validates:

case class Customer(firstName: String) {

  def updatePrefs(changes: CustomerPrefsChanges): Customer = {
    // returns a new customer with updated prefs
  }

  def validate: Customer = {
    // returns this if this is a valid customer. otherwise,
    // throws a validation exception
  }
}

Now we can update the preferences of a persistent Customer like so:

val customerState2 = customerState1.map(_.updatePrefs(changes))

And we can validate like so:

val customerState3 = customerState2.map(_.validate)

The following service method looks up a Customer by natural key, updates the preferences, validates, and persists the results:

def updateCustomerPrefs(
  key: NaturalKey[Customer],
  changes: CustomerPrefsChanges): Unit = {
  val retrieved = customerRepo.retrieve(key)
  val updated = retrieved.map(_.updatePrefs(changes))
  val validated = updated.map(_.validate)
  customerRepo.update(validated)
}

You could fit the body of that method on a single line by inlining the local variables.

It may seem strange to consider your entities as immutable. After all, an entity is something that has a fixed identity and changes over time. But with a subtle change of mindset, we can think of the entity object as the current state of the entity, whereas state transitions are extracted from the object API, and placed into functions and service methods.

The most important thing to take forward from all this is that we should encapsulate the persistent state within the repository layer whenever possible. Making these fields private in the domain classes, with no getters and setters, is a good first step. But it is still less than ideal. Encapsulating things further provides for a workable but clunky interface in Java that feels much more natural in an F/OO language like Scala.

In the next essay, we will continue to look at immutability in our domain objects by considering the differences between entities and value objects in Domain Driven Design.

No comments:

Post a Comment