When we talk about the algebra of an operation, we talk about the signature of the operation, and how, starting from the input, we can just "follow the types" to lead to the implementation of that operation.
Consider the following operation generateTrades
as part of a domain service TradingService
. The idea is to generate all trades that happened on the day (input to the operation) and executed by the user (input to the operation) in the back-office of a securities trading organization.
In the example below, note the following 2 principles that form the core of the algebraic modeling:
- The sequence of steps mentioned in the specification of the method as comments and correspond one-to-one to the types of the operations used in implementing
generateTrades
. I have annotated the steps in the implementation below. - The implementation of
generateTrades
is completely decoupled from the implementation of the operations likequeryExecutionsForDate
,getAccountNoFromExecution
etc. which we use here. Just follow the types of these operations and compose using the appropriate combinators.
This is the essence of algebraic domain modeling. The implementation evolves from the specification through proper composition of types that model the domain.
trait TradingService:
/** Generate trades for the day and by a specific user. Here are the steps:
*
* 1. Query all executions for the day
* 2. Group executions by order no
* 3. For each order no, get the account no from the order details
* 4. Allocate the trade to the client account
* 5. Store the trade
*/
def generateTrades(
date: LocalDate,
userId: UserId
): ZStream[Any, Throwable, Trade] =
queryExecutionsForDate(date) // 1
.groupByKey(_.orderNo): // 2
case (orderNo, executions) =>
executions
.via(getAccountNoFromExecution) // 3
.via(allocateTradeToClientAccount(userId)) // 4
.via(storeTrades) // 5
/** Get client account numbers from a stream of executions
*/
def getAccountNoFromExecution: ZPipeline[Any, Throwable, Execution, (Execution, AccountNo)]
/** Generate trades from executions and allocate to the associated client account
*/
def allocateTradeToClientAccount(userId: UserId): ZPipeline[Any, Throwable, (Execution, AccountNo), Trade]
/** persist trades to database and return the stored trades
*/
def storeTrades: ZPipeline[Any, Throwable, Trade, Trade]
/** query all exeutions for the day
*/
def queryExecutionsForDate(date: LocalDate): ZStream[Any, Throwable, Execution]
For a complete implementation of algebraic domain modeling techniques, take a look at https://github.com/debasishg/tradeio3/tree/main.