Case classes also offer pattern
matching by virtue of their magical
autogeneration of the extractors. We
used pattern matching on case classes
when we designed our DSL. For more
details on how case classes make good
algebraic data types, refer to Programming in Scala. 2
PartialFunction[Market,
=> NetAmount].
Trade val forDefault: CashValueCalcu-
lationStrategy = {
//.. logic for cash value calcu-
lation for other markets
}
}
The Embedded DSL
Before we dig into the implementation of the DSL that models the net
cash-value calculation of a trade, here
are some of the business rules that we
must consider in the design:
˲ ˲ Net cash-value calculation logic
varies with the market where the trade
is being executed.
˲ ˲ We can have specific market rules
for individual markets such as Hong
Kong or Singapore.
˲ ˲ We can have default rules that apply to all other markets.
˲ ˲ If required, the user can also specify custom strategies and domain-specific optimizations for cash-value
calculation in the DSL.
In the example, the DSL constructs
are designed as linguistic abstractions
on top of the domain model. Business
users have a major role to play in collaborating with the developers to ensure the right amount of expressiveness is put in the published syntax. It
must be loosely coupled from the core
abstractions (Trade, Account, Instrument, and so on) and must speak
the domain language of the users. The
DSL syntax also needs to be composable, so that users can extend the language with custom domain logic on
top of what the base language offers.
Once you have the syntactic constructs, you can use them to develop
the application business rules. In the
following example we develop the
business rule for the cash-value calculation logic of trades on top of the
syntax the DSL publishes.
Scala offers a rich type system we
can use to model some of the business
rules. We model the cash-value cal-
culation logic of a trade as a function
from Trade to NetAmount, which is
expressed in Scala as Trade => NetA-
mount. Now each such strategy of cal-
culation is driven by a Market, which
means every such function is defined
only for a specific value of the Market.
We model this as:
Besides expressing the market-
based dispatch structure of the calcu-
lation logic as an abstract data type,
PartialFunction in Scala is exten-
sible and can be chained together
using combinators such as andThen
and orElse. For more details on how
to compose using PartialFunction,
refer to the Scala Web site. 5
type NetAmount = BigDecimal
type CashValueCalculationStrategy
= PartialFunction[Market, Trade
=> NetAmount]
This strategy is selected for any
market for which it is used. The “_” is
a placeholder that matches any market passed to it.
A DSL is useful when the user can
compose multiple DSL abstractions
to form larger ones. In our case we
have designed individual snippets
for selecting the appropriate strategy
that calculates the net cash value of
a trade. How do we compose them so
the user can use the DSL without caring about the individual market-spe-cific dispatch logic?
We use an orElse combinator that
traverses the chain of individual Par-
tialFunctions and selects the first
matching market. If no market-spe-
cific strategy is found, then it selects
the default. Here is how we wire these
snippets together:
As the problem domain suggests,
we can have a specialized strategy of
the cash-value calculation logic for
specific markets. As an example, here
is how we model a DSL for the Hong-
Kong market:
val forHongKong: CashValueCal-
culationStrategy = {
}
}
lazy val cashValueComputation:
CashValueCalculationStrategy =
Note how this abstraction is free
of unnecessary complexity. It is defined only for the HongKong market
and returns a function that accepts
a trade and returns a calculated cash
value. (The actual logic of calculation
is elided and may not be relevant to
the current context.) Similarly, we can
define another specialization for the
Singapore market:
val forSingapore: CashValueCal-
culationStrategy = {
}
}
Let’s see how the default strategy is
selected through a match-any-market
parameter:
This is the DSL that does a dynamic
dispatch for the appropriate cash-value
calculation strategy together with a fall-
back for the default. It addresses the
first three business rules enumerated
at the beginning of the section. The
abstraction above is concise, speaks
the domain language, and makes the
sequencing of the dispatch logic very
explicit. A business user who is not a
programmer will be able to verify the
appropriate domain rule.