An aggregate in domain driven design is a published abstraction that provides a single point of interaction to a specific domain concept. Considering the classes I introduced in the last post, an
Order
is an aggregate. It encapsulates the details that an Order
is composed of in the real world (well, only barely in this example, which is only for illustration purposes :-)). Note an aggregate can consist of other aggregates - e.g. we have a
Customer
instance within an Order
. Eric Evans in his book on Domain Driven Design provides an excellent discussion of what constitutes an Aggregate.Functional Updates of Aggregates with Lens
This is not a post about Aggregates and how they fit in the realm of domain driven design. In this post I will talk about how to use some patterns to build immutable aggregates. Immutable data structures offer a lot of advantages, so build your aggregates ground up as immutable objects. Algebraic Data Type (ADT) is one of the patterns to build immutable aggregate objects. Primarily coming from the domain of functional programming, ADTs offer powerful techniques of pattern matching that help you match values against patterns and bind variables to successful matches. In Scala we use case classes as ADTs that give immutable objects out of the box ..
case class Order(orderNo: String, orderDate: Date, customer: Customer, lineItems: Vector[LineItem], shipTo: ShipTo, netOrderValue: Option[BigDecimal] = None, status: OrderStatus = Placed)
Like all good aggregates, we need to provide a single point of interaction to users. Of course we can access all properties using accessors of case classes. But what about updates ? We can update the
orderNo
of an order like this ..val o = Order( .. ) o.copy(orderNo = newOrderNo)
which gives us a copy of the original order with the new order no. We don't mutate the original order. But anybody having some knowledge of Scala will realize that this becomes pretty clunky when we have to deal with nested object updation. e.g in the above case,
ShipTo
is defined as follows ..case class Address(number: String, street: String, city: String, zip: String) case class ShipTo(name: String, address: Address)
So, here you go in order to update the zip code of a
ShipTo
..val s = ShipTo("abc", Address("123", "Monroe Street", "Denver", "80233")) s.copy(address = s.address.copy(zip = "80231"))
Not really pleasing and can go off bounds in comprehensibility pretty soon.
In our domain model we use an abstraction called a
Lens
for updating Aggregates. In very layman's terms, a lens is an encapsulated get
and set
combination. The get
extracts a small part from a larger whole, while the set
transforms the larger abstraction with a smaller part taken as a parameter. case class Lens[A, B](get: A => B, set: (A, B) => A)
This is a naive definition of a
Lens
in Scala. Sophisticated lens
designs go a long way to ensure proper abstraction and composition. scalaz provides one such implementation out of the box that exploits the similarity in structure between the get
and the set
to generalize the lens definition in terms of another abstraction named Store
. As it happens so often in functional programming, Store
happens to abstract yet another pattern called the Comonad
. You can think of a Comonad
as the inverse of a Monad
. But in case you are more curious, and have wondered how lenses form "the Coalgebras for the Store Comonad", have a look at the 2 papers here and here.Anyway for us mere domain modelers, we will use the
Lens
implementation as in scalaz .. here's a lens that helps us update the OrderStatus
within an Order
..val orderStatus = Lens.lensu[Order, OrderStatus] ( (o, value) => o.copy(status = value), _.status )
and use it as follows ..
val o = Order( .. ) orderStatus.set(o, Placed)
will change the
status
field of the Order
to Placed
. Let's have a look at some of the compositional properties of a lens which help us write readable code for functionally updating nested structures. Composition of Lenses
First let's define some individual lenses ..// lens for updating a ShipTo of an Order val orderShipTo = Lens.lensu[Order, ShipTo] ( (o, sh) => o.copy(shipTo = sh), _.shipTo ) // lens for updating an address of a ShipTo val shipToAddress = Lens.lensu[ShipTo, Address] ( (sh, add) => sh.copy(address = add), _.address ) // lens for updating a city of an address val addressToCity = Lens.lensu[Address, String] ( (add, c) => add.copy(city = c), _.city )
And now we compose them to define a lens that directly updates the
city
of a ShipTo
belonging to an Order
..// compositionality FTW def orderShipToCity = orderShipTo andThen shipToAddress andThen addressToCity
Now updating a
city
of a ShipTo
in an Order
is as simple and expressive as ..val o = Order( .. ) orderShipToCity.set(o, "London")
The best part of using such compositional data structures is that it makes your domain model implementation readable and expressive to the users of your API. And yet your aggregate remains immutable.
Let's look at another use case when the nested object is a collection. scalaz offers partial lenses that you can use for such composition. Here's an example where we build a lens that updates the value member within a
LineItem
of an Order
. A LineItem
is defined as ..case class LineItem(item: Item, quantity: BigDecimal, value: Option[BigDecimal] = None, discount: Option[BigDecimal] = None)
and an
Order
has a collection of LineItem
s. Let's define a lens that updates the value
within a LineItem
..val lineItemValue = Lens.lensu[LineItem, Option[BigDecimal]] ( (l, v) => l.copy(value = v), _.value )
and then compose it with a partial lens that helps us update a specific item within a vector. Note how we convert our
lineItemValue
lens to a partial lens using the unary operator ~
..// a lens that updates the value in a specific LineItem within an Order def lineItemValues(i: Int) = ~lineItemValue compose vectorNthPLens(i)
Now we can use this composite lens to functionally update the
value
field of each of the items in a Vector
of LineItem
s using some specific business rules ..(0 to lis.length - 1).foldLeft(lis) {(s, i) => val li = lis(i) lineItemValues(i).set(s, unitPrice(li.item).map(_ * li.quantity)).getOrElse(s) }
In this post we saw how we can handle aggregates functionally and without any in-place mutation. This keeps the model pure and helps us implement domain models that has sane behavior even in concurrent settings without any explicit use of locks and semaphores. In the next post we will take a look at how we can use such compositional structures to make the domain model speak the ubiquitous language of the domain - another pattern recommended by Eric Evans in domain driven design.
2 comments:
This has been a great series of posts. I'm very interested in how functional concepts can fulfill DDD promises. Looking forward to the next article!
This is a good informative article, it helps a lot for me....., nice way of defining.
Post a Comment