I just started diving into F# language and recently read an article about Event Sourcing in functional style and have some questions to clarify. There was the following command handler presented:
let execute state command =
let event =
match command with
| AddTask args ->
args.Id
|> onlyIfTaskDoesNotAlreadyExist state
|> (fun _ -> TaskAdded args)
| RemoveTask args ->
args.Id
|> onlyIfTaskExists state
|> (fun _ -> TaskRemoved args)
| ClearAllTasks -> AllTasksCleared
| CompleteTask args ->
args.Id
|> onlyIfTaskExists state
|> (fun _ -> TaskCompleted args)
| ChangeTaskDueDate args ->
args.Id
|> (onlyIfTaskExists state >> onlyIfNotAlreadyFinished)
|> (fun _ -> TaskDueDateChanged args)
event |> List.singleton //we must return list of events
It's pretty simple when one command produces one event. But how to deal with case when state of the aggregate must be updated before some newer events will be produced?
Consider the following example: you have an Order aggregate and an OrderLine entity inside it. User can add or remove order lines and every time he did this order's total sum must be calculated. It can be implemented by three functions:
let addOrderLine order catalogItem = if (isOpen order) then Ok OrderLineAdded(catalogItem .Id, catalogItem.Price) else Error OrderAlreadyClosed
let removeOrderLine order lineId = if (isOpen order) then Ok OrderLineRemoved(lineId) else Error OrderAlreadyClosed
let calculateOrder order =
let sum = order.orderLines |> List.sum (fun x -> x.Price)
OrderCalculated sum
So caculateOrder function should be invoked only with actual aggregate's state. That means I need to add order line first, update state with produced OrderLineAdded event and finally execute calculation. After all I will collect all of events produced and return them from handler. It would be very easy to implement such case in OOP style but in FP handler grows in size incredibly fast. It seems like design smell to me.
Should I rethink my design and join OrderLineAdded and OrderCalculated events in one? Or here can be some elegant solution hidded from my eyes?
Any suggestions are welcome
I will add a C# implementation, which doesn't make me question. OOP aggregate is a mutable object, you can update its status easily, what is not functional programming way
public sealed class Order: EventSourcedAggregate
{
public int Id { get; private set; }
public bool isOpen { get; private set; }
public decimal TotalSum { get; private set; }
public List<OrderLine> OrderLines { get; }
public void Apply(OrderLineAdded e) { OrderLines.Add(new OrderLine(e.Id, e.Price));}
public void Apply(OrderCalculated e) { TotalSum = e.Sum;}
public void AddOrderLine(CatalogItem item)
{
if (!isOpen) throw new InvalidOperationException("Order closed exception");
Emit(new OrderLineAdded(item.Id, item.Price)); //Emit method saves event as uncommitted and updates aggregate state
CalculateOrder(); //calculates total sum
}
private void CalculateOrder()
{
var sum = OrderLines.Sum(x => x.Price);
Emit(new OrderCalculated(sum));
}
}
Disclaimer: I'm not an F# dev by any means, so will answer this in Scala.
Remember that you have the event-handling function available (because your aggregate is defined by the pair of the event handler and the command handler):
val eventHandler: Event => Aggregate => Aggregate = ??? // ??? meaning "not implemented yet, but we know the type it'll be
This does imply that every event, given a handler, is isomorphic to a function from Aggregate
to Aggregate
, which is the most interesting FP interpretation of events (IMO).
So in a command handler, if one for some reason wants the state of the aggregate after applying an event one need only apply the event handler themselves
val commandHandler: Aggregate => Command => (Response, Seq[Event]) = state => cmd => {
cmd match {
case AddOrderLine(catalogId, price) =>
if (!isOpen(state)) Error(OrderAlreadyClosed) -> Seq.empty
else {
val added = OrderLineAdded(catalogId, price)
val intermediateState = eventHandler(added)(state)
// explicit iterator to not create an intermediate strict collection
val sum = intermediateState.orderLines.iterator.map(_.price).sum
Ok -> Seq(added, OrderCalculated(sum))
}
}
}
Of course, there's no reason the event handler for OrderLineAdded
couldn't update the sum. Part of the benefit of event sourcing is that your events can designate changes to arbitrarily many properties of an aggregate (a model that has a command and a corresponding event for changing every property of the aggregate where no event changes more than one property is perhaps the canonical example of an anemic domain model). About the only reason I can see it making sense to apply the event handler in the command handler is a case where deciding that a given command is invalid would be effectively duplicating the event handler's logic and making the resulting state impossible to represent has an adverse effect on overall code clarity or is not easily expressible in your language's type system: sort of the "f around, find out, and then forget it ever happened" approach. An example might be some domain constraint that the total cost of a valid order must never be an the product of an even number and a perfect square greater than 4 (so $18, $32, $36, $50, $54, $64, etc. are forbidden).