Functions compose more naturally than objects and In this post I will use functional programming idioms to implement one of the patterns that form the core of domain driven design - the Specification pattern, whose most common use case is to implement domain validation. Eric's book on DDD says regarding the Specification pattern ..
It has multiple uses, but one that conveys the most basic concept is that a SPECIFICATION can test any object to see if it satisfies the specified criteria.A specification is defined as a predicate, whereby business rules can be combined by chaining them together using boolean logic. So there's a concept of composition and we can talk about Composite Specification when we talk about this pattern. Various literature on DDD implement this using the Composite design pattern so commonly implemented using class hierarchies and composition. In this post we will use function composition instead.
Specification - Where ?
One of the very common confusions that we have when we design a model is where to keep the validation code of an aggregate root or any entity, for that matter.- Should we have the validation as part of the entity ? No, it makes the entity bloated. Also validations may vary based on some context, while the core of the entity remains the same.
- Should we have validations as part of the interface ? May be we consume JSON and build entities out of it. Indeed some validations can belong to the interface and don't hesitate to put them there.
- But the most interesting validations are those that belong to the domain layer. They are business validations (or specifications), which Eric Evans defines as something that "states a constraint on the state of another object". They are business rules which the entity needs to honor in order to proceed to the next stage of processing.
Order
entity and the model identifies the following domain "specifications" that a new Order
must satisfy before being thrown into the processing pipeline:- it must be a valid order obeying the constraints that the domain requires e.g. valid date, valid no of line items etc.
- it must be approved by a valid approving authority - only then it proceeds to the next stage of the pipeline
- customer status check must be passed to ensure that the customer is not black-listed
- the line items from the order must be checked against inventory to see if the order can be fulfilled
An important point to note here is that none of the above steps mutate the order - so every specification gets a copy of the original
Order
object as input, on which it checks some domain rules and determines if it's suitable to be passed to the next step of the pipeline.Jumping on to the implementation ..
Let's take down some implementation notes from what we learnt above ..- The
Order
can be an immutable entity at least for this sequence of operations - Every specification needs an order, can we can pull some trick out of our hat which prevents this cluttering of API by passing an
Order
instance to every specification in the sequence ? - Since we plan to use functional programming principles, how can we model the above sequence as an expression so that our final result still remains composable with the next process of order fulfilment (which we will discuss in a future post) ?
- All these functions look like having similar signatures - we need to make them compose with each other
type ValidationStatus[S] = \/[String, S] type ReaderTStatus[A, S] = ReaderT[ValidationStatus, A, S] object ReaderTStatus extends KleisliInstances with KleisliFunctions { def apply[A, S](f: A => ValidationStatus[S]): ReaderTStatus[A, S] = kleisli(f) }
ValidationStatus
defines the type that we will return from each of the functions. It's either some status S
or an error string that explains what went wrong. It's actually an Either
type (right biased) as implemented in scalaz.One of the things which we thought will be cool is to avoid repeating the
Order
parameter for every method when we invoke the sequence. And one of the idioamtic ways of doing it is to use the Reader monad. But here we already have a monad - \/
is a monad. So we need to stack them together using a monad transformer. ReaderT
does this job and ReaderTStatus
defines the type that somehow makes our life easier by combining the two of them.The next step is an implementation of
ReaderTStatus
, which we do in terms of another abstraction called Kleisli
. We will use the scalaz library for this, which implements ReaderT
in terms of Kleisli
. I will not go into the details of this implementation - in case you are curious, refer to this excellent piece by Eugene.So, how does one sample specification look like ?
Before going into that, here are some basic abstractions, grossly simplified only for illustration purposes ..
// the base abstraction sealed trait Item { def itemCode: String } // sample implementations case class ItemA(itemCode: String, desc: Option[String], minPurchaseUnit: Int) extends Item case class ItemB(itemCode: String, desc: Option[String], nutritionInfo: String) extends Item case class LineItem(item: Item, quantity: Int) case class Customer(custId: String, name: String, category: Int) // a skeleton order case class Order(orderNo: String, orderDate: Date, customer: Customer, lineItems: List[LineItem])
And here's a specification that checks some of the constraints on the
Order
object ..// a basic validation private def validate = ReaderTStatus[Order, Boolean] {order => if (order.lineItems isEmpty) left(s"Validation failed for order $order") else right(true) }
It's just for illustration and does not contain much domain rules. The important part is how we use the above defined types to implement the function.
Order
is not an explicit argument to the function - it's curried. The function returns a ReaderTStatus
, which itself is a monad and hence allows us to sequence in the pipeline with other specifications. So we get the requirement of sequencing without breaking out of the expression oriented programming style.Here are a few other specifications based on the domain knowledge that we have gathered ..
private def approve = ReaderTStatus[Order, Boolean] {order => right(true) } private def checkCustomerStatus(customer: Customer) = ReaderTStatus[Order, Boolean] {order => right(true) } private def checkInventory = ReaderTStatus[Order, Boolean] {order => right(true) }
Wiring them together
But how do we wire these pieces together so that we have the sequence of operations that the domain mandates and yet all goodness of compositionality in our model ? It's actually quite easy since we have already done the hard work of defining the appropriate types that compose ..Here's the
isReadyForFulfilment
method that defines the composite specification and invokes all the individual specifications in sequence using for-comprehension, which, as you all know does the monadic bind in Scala and gives us the final expression that needs to be evaluated for the Order
supplied.def isReadyForFulfilment(order: Order) = { val s = for { _ <- validate _ <- approve _ <- checkCustomerStatus(order.customer) c <- checkInventory } yield c s(order) }
So we have the monadic bind implement the sequencing without breaking the compositionality of the abstractions. In the next instalment we will see how this can be composed with the downstream processing of the order that will not only read stuff from the entity but mutate it too, of course in a functional way.