This is the fifth and final part of this series. In this last part we’ll reduce the boilerplate code related to handling events and as a bonus we’ll also make handling validation a bit nicer. But before we take a deep dive into the code, let’s consider the design of the last three Invoice implementations.
Since this part is mostly about reducing boilerplate from the typed implementation of invoice without significantly affecting the design of our domain classes, it makes sense to take a quick look at the design of the three event sourced implementations so far: mutable (part 2), immutable (part 3), and typed/monadic (part 4/5). Which one, if any, is the “best”? Let’s compare them based on XP’s rules of simple design.
Rule | Mutable | Immutable | Typed / Monadic |
---|---|---|---|
Pass all tests | ++ | ++ | ++ |
Clear, expressive, & consistent | + | + | ++ |
No duplication | – | – | + |
Minimal methods, classes | + | + | -/+ |
So all implementations pass the tests and therefore satisfy the requirements. Splitting the invoice class into three subclasses improved expressiveness and reduced duplication (no need for some runtime checks), at the cost of adding some new methods and classes.
But the main highlight is that we were able to radically redesign and reimplement our domain code with minimal impact on our clients or infrastructure. We still generate exactly the same events as in the first mutable event sourced implementation and only the calling syntax was slightly changed (which usually is contained in a controller or service layer anyway). The reporting side of the application, any views, integration with external systems, etc. are unaffected!
So by having our events as a stable abstraction we have greatly decoupled the components of our application while making each component more cohesive. This gives us confidence that as requirements change we can keep our domain clean and simple, without needing to compromise our implementation with respect to durability or reporting needs.
So now that we have this out of the way, let’s get back into the code.
Let’s take a look at the applyEvent
method in the DraftInvoice
class from part 4:
def applyEvent = {
case event: InvoiceRecipientChanged => applyRecipientChanged(event)
case event: InvoiceItemAdded => applyItemAdded(event)
case event: InvoiceItemRemoved => applyItemRemoved(event)
case event: InvoiceSent => applySent(event)
case event => unhandled(event)
}
It’s basically checking the type of the event and then dispatching to the correct event handler using a case-block. Each event handler is simply a function that takes an event of the correct type and returns the appropriate response. We could try to use some reflection magic to remove the need for this method, but that feels like cheating. Let’s try to if we can compose our typed event handlers into the generic applyEvent
method instead.
Fortunately, Scala has a built-in trait called PartialFunction
that has the useful method orElse
that does exactly what we need. We just need to turn our event handling functions into partial functions to make this work.
We do this by making the type of our handlers explicit using a new class EventHandler
and adding an implicit conversion from our handler type to partial functions. The partial function checks the event’s type and invoke the handler if the type is correct:
protected class EventHandler[Event, +Result](callback: Event => Result) {
def apply(event: Event) = // [... code omitted ...]
def applyFromHistory(event: Event) = callback(event)
}
protected def handler[A, B](callback: A => B) = new EventHandler(callback)
implicit protected def handlerToPartialFunction[A, B](handler: EventHandler[A, B])(implicit m: Manifest[A]) =
new PartialFunction[AnyRef, B] {
def isDefinedAt(event: AnyRef) = m.erasure.isInstance(event)
def apply(event: AnyRef) = handler.applyFromHistory(event.asInstanceOf[A])
}
Here an instance of EventHandler
can be created using the handler
method. The EventHandler
class has a applyFromHistory
method which simply passes the event to the provided callback.
The handlerToPartialFunction
takes an EventHandler
and an implicit Manifest and turns it into a partial function that takes an event of type AnyRef
(Scala’s equivalent of Java’s Object
class). When the partial function is invoked it downcasts the event and delegates to the EventHandler
‘s applyFromHistory
method. But the partial function is only defined when the provided event’s type matches the type expected by the event handler!
Using these definitions we can now update our typed event handlers and easily compose them for use with applyEvent
, using the standard PartialFunction.orElse
method:
def applyEvent = applyRecipientChanged orElse applyItemAdded orElse applyItemRemoved orElse applySent
private def applyRecipientChanged = handler {event: InvoiceRecipientChanged =>
copy(event :: uncommittedEvents, recipient_? = event.recipient.isDefined)
}
Now the applyEvent
method is just a single line listing all the applicable event handlers, without all the boilerplate. The typed event handlers are slightly changed to include the call to the handler
factory method.
The other part of boilerplate code was related to storing and managing the list of uncommitted events. Each aggregate root subclass needed to provide an accessor for the uncommittedEvent
field and implement the markCommitted
method. Each event handler then had to ensure the new event was prepended to the list of uncommitted event, which is somewhat error-prone.
The first step to fixing this is to remove the list of uncommitted events from the aggregate root and to start explicitly passing it into our methods, which then returns the updated list together with the updated invoice. This will certainly clean up the invoice subclasses, such as the PaidInvoice
listed below.
class PaidInvoice extends Invoice {
def applyEvent = unhandled
}
Unfortunately it makes methods like pay
and applyPaymentReceived
rather ugly, and we’re not even talking yet about the client code which now needs to manually manage the list of uncommitted events:
def pay(uncommittedEvents: List[Any]) =
applyPaymentReceived(InvoicePaymentReceived(id, new LocalDate), uncommittedEvents)
private def applyPaymentReceived(event: InvoicePaymentReceived, uncommittedEvents: List[Any]) =
(event :: uncommittedEvents, new PaidInvoice)
Fortunately, monads can help us with that. But first we’ll make our types explicit (a recurring theme of this series).
Let’s look at the types we have now. The pay
method above has the type List[Any] => (List[Any], PaidInvoice)
. Let’s call the type of pay
a Behavior
which returns a Reaction
when triggered by passing it a list of events. A reaction can either be Accepted
for success or Rejected
when failed:
trait Reaction[+T]
case class Accepted[+T](events: List[Any], result: T) extends Reaction[T]
case class Rejected(message: String) extends Reaction[Nothing]
trait Behavior[+A] {
protected def apply(events: List[Any]): Reaction[A]
// [... code omitted ...]
def reaction = apply(Nil)
def changes = reaction.asInstanceOf[Accepted[_]].events
def rejected = reaction.asInstanceOf[Rejected].message
}
The Behavior
class defines an abstract method apply
that takes the current list of uncommitted events as argument and should implement the specific behavior we want. It also adds a reaction
method that invokes the behavior with the empty list. The changes
and rejected
methods are just there for convenience.
We’ll also define a few additional methods to create some useful behaviors:
object Behaviors {
def behavior[T](callback: List[Any] => Reaction[T]) = new Behavior[T] {
protected def apply(events: List[Any]) = callback(events)
}
def accept[T](result: T) = behavior(events => Accepted(events, result))
def reject(message: String) = behavior(_ => Rejected(message))
def record(event: Any) = behavior(events => Accepted(event :: events, ()))
def guard(condition: Boolean, message: => String) = if (condition) accept() else reject(message)
}
The behavior
method lets us easily create a new behavior by providing a callback, instead of having to define a new anonymous subclass implementation every time.
Accept
doesn’t modify the list of uncommitted events but simple returns a specific result, reject
forgets about any uncommitted events and returns an error message, record
records the provided event and returns an uninteresting value, and guard
rejects when the condition does not hold and returns an uninteresting value otherwise.
Now we’ll just need two more pieces to complete the puzzle. The first thing we need to be able to do is to compose two behaviors into a single new behavior. We do this by first triggering the first behavior, and if successful, passing the result from the first behavior into the second behavior. This is the monad bind
operation, which is called flatMap
in Scala and we make it part of our Behavior
trait:
trait Behavior[+A] {
protected def apply(events: List[Any]): Reaction[A]
def flatMap[B](next: A => Behavior[B]) = behavior {events =>
this(events) match {
case Accepted(updatedEvents, result) => next(result)(updatedEvents)
case Rejected(message) => Rejected(message)
}
}
// [... code omitted ...]
}
With all of this in place, we can define the EventHandler.apply
method which is used by our invoice implementation to call event handlers when not reloading from history:
protected class EventHandler[Event, +Result](callback: Event => Result) {
def apply(event: Event) = record(event) flatMap (_ => accept(callback(event)))
// [... code omitted ...]
}
The apply
method simply records the event and then accepts whatever the result of the event handler’s callback is. The return value of record
is ignored, since it is of type Unit
and not interesting.
So let’s take a look at the DraftInvoice.send
method of our new Invoice.scala:
def send: Behavior[SentInvoice] =
guard(readyToSend_?, "recipient and items must be specified before sending") flatMap {_ =>
val now = new LocalDate
applySent(InvoiceSent(id, sentDate = now, dueDate = now.plusDays(14)))
}
// [... code omitted ...]
private def applySent = handler {event: InvoiceSent => new SentInvoice(id, event.dueDate)}
This send
method’s only changes are the return type (Behavior[SentInvoice]
) and the use of the guard
method to perform validation. Again flatMap
is used to sequence the behavior. So if the guard fails the applySent
is never performed. The implementation of applySent
is now also cleaned up and no longer has to worry about recording the event as this is taken care of by EventHandler.apply
.
Client code is now unfortunately a bit more complicated, since it needs to deal with the monad:
"draft invoice" should {
val invoice: DraftInvoice = Invoice.loadFromHistory(Seq(InvoiceCreated(1)))
"support adding invoice items" in {
val updated = invoice.addItem("Food", "2.95") flatMap (_.addItem("Water", "1.95")) flatMap (_.removeItem(1))
updated.changes must contain(InvoiceItemAdded(1, InvoiceItem(1, "Food", "2.95"), "2.95"))
updated.changes must contain(InvoiceItemAdded(1, InvoiceItem(2, "Water", "1.95"), "4.90"))
updated.changes must contain(InvoiceItemRemoved(1, InvoiceItem(1, "Food", "2.95"), "1.95"))
}
"not be ready to send" in {
invoice.send.rejected must beEqualTo("recipient and items must be specified before sending")
}
}
Here the need to sequence the different behaviors using flatMap
is ugly. Fortunately, this can be improved by using a more convenient name (bind
or >>=
) and in many cases, a service will only invoke a single method on an aggregate at a time, so the need for sequencing behaviors may be rare. Validation checking has improved, since there is no longer a need to catch exceptions, improving readability and making it easier to combine multiple validation results into one.
This series of blog posts has taken us through a whirlwind tour of modeling a simple invoice example. Starting out with a straightforward JPA implemention we quickly moved to event sourcing that made it possible to implement an immutable domain model. This allowed us to raise the level of abstraction of our code, increase clarity, and increase type safety. Finally we used some functional programming techniques to reduce boilerplate. Even with the additional classes and event handling methods, the monadic version of Invoice
is only slightly larger than the mutable event sourced invoice. The test code is significantly smaller, since it no longer needs to test for various run-time checks, as the compiler takes care of that.
But the most significant advantage of event sourcing is the ability to change the implementation of the domain in radical ways, making it possible to keep up with changing business requirements and allowing us to keep improving the domain as our knowledge and understanding improves. All this with very little impact on the rest of the system and no need to perform database migrations.
Furthermore, our reporting and querying needs are also decoupled from the domain. Now we can easily use an RDBMS, document store, lucene index, graph database, and/or anything else that is most appropriate for our specific querying and reporting needs, without any impact on our domain model.
Finally, critical business data has become much more durable, as we only add events, and never update or delete. Full historical data is maintained, which potentially holds a great amount of business value. We now also have access to full audit logs for regulatory or debugging purposes, etc. This is incredibly valuable!
The design space for CQRS, event sourcing, and immutable domain models is still wide open. It will be very interesting to see how this evolves, and when and where these techniques are applicable. Certainly it makes it possible to apply standard functional programming techniques to “inherently” mutable business domains, with all the goodness that entails.