The Transaction Script pattern organizes business logic as a single procedure. It has always been considered less sophisticated and flexible than a layered architecture with a rich domain model. But is that really true?
In this talk, we'll reinvent the Transaction Script using functional programming principles. We'll see that we can still do domain-driven design, and still have code which is decoupled and reusable, all while preserving the simplicity and productivity of the original one-script-per-workflow approach.
7. Transaction Script
⢠Most business applications can be thought of
as a series of transactions.
⢠A "Transaction Script" organizes this logic as a
single procedure, making calls directly to the
database or through a thin database wrapper.
⢠Each transaction will have its ownTransaction
Script
8. The PEAA patterns for domain logic
⢠Transaction script: each procedure handles a
single request
⢠Table Module: one instance handles the
business logic for all rows in a database
table/view.
⢠Domain Model: An object model of the domain
that incorporates both behavior and data.
⢠Service Layer: a layer of services that
establishes a set of available operations
9. The PEAA patterns for domain logic
⢠Transaction script
⢠Table Module
⢠Domain Model
⢠Service Layer
Too simple, too "anemic"
Too db centric
Nice and complex!
11. Transaction scripts have a bad rep
⢠The reputation
â It's for people who are stuck in the 1990s
â It's for people who use Cobol
â It's only for uncool people
⢠The truth
â It can be reinvented using functional programming
â Therefore, it can now be cool again!
13. A typical transaction script
get data from DB
do some business logic
write some data to DB
some more business logic
if some condition then
some more DB access
some more business logic
some more DB access
else
some more business logic
some more DB access
Pro: Easy to understand.
One directional.
Pro: Focused!
All code is relevant.
YAGNI for free
14. A typical transaction script
get data from DB
do some business logic
write some data to DB
some more business logic
if some condition then
some more DB access
some more business logic
some more DB access
else
some more business logic
some more DB access
Con: Hard to modify
15. A typical transaction script
get data from DB
do some business logic
write some data to DB
some more business logic
if some condition then
some more DB access
some more business logic
some more DB access
else if some other condition then
some more business logic
else
some more business logic
some more DB access
Con: Hard to modify
and evolve.
No graceful path to
richer behavior
16. A typical transaction script
get data from DB
do some business logic
write some data to DB
some more business logic
if some condition then
some more DB access
some more business logic
some more DB access
else
some more business logic
some more DB access
Con: Hard to reuse.
No rich domain model.
How do you reuse this bit only?
17. A typical transaction script
get data from DB
do some business logic
write some data to DB
some more business logic
if some condition then
some more DB access
some more business logic
some more DB access
else
some more business logic
some more DB access
Con: Hard to test
Business logic is
mixed up with DB access
18. A typical transaction script
get data from DB
do some business logic
write some data to DB
some more business logic
if some condition then
some more DB access
some more business logic
some more DB access
else
some more business logic
some more DB access
Con: Hard to test
How can you test
just these bits?
19. A typical transaction script
get data from DB
do some business logic
write some data to DB
some more business logic
if some condition then
some more DB access
some more business logic
some more DB access
else
some more business logic
some more DB access
Can we fix
these problems?
Yes, we can!
29. Lego Philosophy
1. All pieces are designed to be connected
2. The pieces are reusable in many contexts
3. Connect two pieces together and get
another "piece" that can still be connected
47. New types are built from smaller types by:
Composing with âANDâ
Composing with âORâ
48. Example: pairs, tuples, records
FruitSalad = One each of and and
Compose with âANDâ
type FruitSalad = {
Apple: AppleVariety
Banana: BananaVariety
Cherry: CherryVariety
}
49. Snack = or or
Compose with âORâ
type Snack =
| Apple of AppleVariety
| Banana of BananaVariety
| Cherry of CherryVariety
51. Example of some requirements:
We accept three forms of payment:
Cash, PayPal, or Card.
For Cash we don't need any extra information
For PayPal we need an email address
For Cards we need a card type and card number
52. interface IPaymentMethod
{..}
class Cash() : IPaymentMethod
{..}
class PayPal(string emailAddress): IPaymentMethod
{..}
class Card(string cardType, string cardNo) : IPaymentMethod
{..}
In OO design you would probably implement it as an
interface and a set of subclasses, like this:
53. type EmailAddress = string
type CardNumber = string
In FP you would probably implement by composing
types, like this:
54. type EmailAddress = ...
type CardNumber = âŚ
type CardType = Visa | Mastercard
type CreditCardInfo = {
CardType : CardType
CardNumber : CardNumber
}
55. type EmailAddress = ...
type CardNumber = ...
type CardType = ...
type CreditCardInfo = ...
type PaymentMethod =
| Cash
| PayPal of EmailAddress
| Card of CreditCardInfo
56. type EmailAddress = ...
type CardNumber = ...
type CardType = ...
type CreditCardInfo = ...
type PaymentMethod =
| Cash
| PayPal of EmailAddress
| Card of CreditCardInfo
type PaymentAmount = decimal
type Currency = EUR | USD
57. type EmailAddress = ...
type CardNumber = ...
type CardType = ...
type CreditCardInfo = ...
type PaymentMethod =
| Cash
| PayPal of EmailAddress
| Card of CreditCardInfo
type PaymentAmount = decimal
type Currency = EUR | USD
type Payment = {
Amount : PaymentAmount
Currency: Currency
Method: PaymentMethod }
58. type EmailAddress = ...
type CardNumber = ...
type CardType = ...
type CreditCardInfo = ...
type PaymentMethod =
| Cash
| PayPal of EmailAddress
| Card of CreditCardInfo
type PaymentAmount = decimal
type Currency = EUR | USD
type Payment = {
Amount : PaymentAmount
Currency: Currency
Method: PaymentMethod }
70. ⢠Agile contribution:
â Rapid feedback during design
⢠DDD contribution:
â Stakeholders have a shared mental model
â âŚwhich is also represented in the code
How can we do design right?
74. module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two |Three | Four | Five | Six | Seven | Eight
| Nine |Ten | Jack | Queen | King
type Card = Suit * Rank
type Hand = Card list
type Deck = Card list
type Player = {Name:string; Hand:Hand}
type Game = {Deck:Deck; Players: Player list}
type Deal = Deck ââş (Deck * Card)
type PickupCard = (Hand * Card) ââş Hand
Sharedlanguage What DDD source code
*should* look like
(this is F#)
75. module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two |Three | Four | Five | Six | Seven | Eight
| Nine |Ten | Jack | Queen | King
type Card = Suit * Rank
type Hand = Card list
type Deck = Card list
type Player = {Name:string; Hand:Hand}
type Game = {Deck:Deck; Players: Player list}
type Deal = Deck ââş (Deck * Card)
type PickupCard = (Hand * Card) ââş Hand
* means a pair. Choose one from each type
list type is built in
78. module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two |Three | Four | Five | Six | Seven | Eight
| Nine |Ten | Jack | Queen | King
type Card = Suit * Rank
type Hand = Card list
type Deck = Card list
type Player = { Name:string; Hand:Hand }
type Game = { Deck:Deck; Players: Player list }
type Deal = Deck ââş (Deck * Card)
type PickupCard = (Hand * Card) ââş Hand
79. module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two |Three | Four | Five | Six | Seven | Eight
| Nine |Ten | Jack | Queen | King
type Card = Suit * Rank
type Hand = Card list
type Deck = Card list
type Player = { Name:string; Hand:Hand }
type Game = { Deck:Deck; Players: Player list }
type Deal = Deck ââş (Deck * Card)
type PickupCard = (Hand * Card) ââş Hand
80. module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two |Three | Four | Five | Six | Seven | Eight
| Nine |Ten | Jack | Queen | King
type Card = Suit * Rank
type Hand = Card list
type Deck = Card list
type Player = { Name:string; Hand:Hand }
type Game = { Deck:Deck; Players: Player list }
type Deal = Deck ââş (Deck * Card)
type PickupCard = (Hand * Card) ââş Hand
Can non-programmers provide
useful feedback?
81. module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two |Three | Four | Five | Six | Seven | Eight | âŚ
type Card = Suit * Rank
type Hand = Card list
type Deck = Card list
type Player = { Name:string; Hand:Hand }
type Game = { Deck:Deck; Players: Player list }
type Deal = Deck ââş (Deck * Card)
type PickupCard = (Hand * Card) ââş Hand
82. module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two |Three | Four | Five | Six | Seven | Eight | âŚ
type Card = Suit * Rank
type Hand = Card list
type Deck = Card list
type Player = { Name:string; Hand:Hand }
type Game = { Deck:Deck; Players: Player list }
type Deal = Deck ââş (Deck * Card)
type PickupCard = (Hand * Card) ââş Hand
83. In the real world
Suit
Rank
Card
Hand
Deck
Player
Deal
In the code
Suit
Rank
Card
Hand
Deck
Player
Deal
84. In the real world
Suit
Rank
Card
Hand
Deck
Player
Deal
In the code
Suit
Rank
Card
Hand
Deck
Player
Deal
PlayerController
DeckBase
AbstractCardProxyFactoryBean
ďť
85. module CardGame =
type Suit = Club | Diamond | Spade | Heart
type Rank = Two |Three | Four | Five | Six | Seven | Eight | âŚ
type Card = Suit * Rank
type Hand = Card list
type Deck = Card list
type Player = { Name:string; Hand:Hand }
type Game = { Deck:Deck; Players: Player list }
type Deal = Deck ââş (Deck * Card)
type PickupCard = (Hand * Card) ââş Hand
89. Why "Bounded Context"?
⢠"Context"
â Specialized knowledge and common language
â Information taken out of context is confusing or
unusable
⢠"Bounded"
â Contexts must be autonomous so they can
evolve independently.
â Boundaries keep you focused! No scope creep!
94. ⢠FP contribution:
â Transactions are functions
â Build them from components using composition
⢠Functional Domain Modeling contribution:
â Use composable types for a rich domain model
â The domain actions are standalone/reusable
â Use autonomous bounded contexts to group and
manage the workflows
Reinventing the Transaction Script
95. Pros and cons of Transaction Scripts
Pros
⢠Easy to understand
⢠Focused: All code is
relevant.
Cons
⢠Hard to modify/evolve
⢠Hard to reuse
⢠No rich domain model
⢠Hard to test
106. Modifying a workflow
Replace a component
Static type checking ensures that
sub-components are used correctly
Minimizing the amount of code that I touch
107. Add features to a workflow
Insert new logic
Minimizing the amount of code that I touch
111. type Deal = Deck ââş (Deck * Card)
type PickupCard = (Hand * Card) ââş Hand
A rich domain model...
These functions are independent, so these
workflows can still evolve independently
...used in functions
114. Traditional layered model
A change to the way that a workflow works
means that you need to touch every layer.
115. Vertical slices
Each workflow contains all the code it needs to get its job done.
When the requirements change for a workflow, only the code in
that particular vertical slice needs to change.
119. Review of Key Testing Concepts
⢠The SUT (System Under Test) should be a unit of
business value
â Test transactions, not classes
⢠Tests should apply to the boundaries of a system
not its internals
â Tests should be done at the transaction level
⢠A "Unit" test means the test is isolated
â That is, it produces no side effects and can be run in
isolation.
â A unit is not a class!
120. In a functional design, all
I/O is at the edges.
A FP-style transaction script
129. 3 different architecturesâŚ
⢠For monoliths:
â Each bounded context is a separate module with a
well-defined interface
⢠For service-oriented architecture:
â Each bounded context is a separate container
⢠For serverless:
â Each individual workflow is deployed separately
135. The ReinventedTransaction Script
⢠Most business applications can be thought of
as a series of transactions.
⢠A "Transaction Script" organizes this logic as a
single function, with a deterministic core
and I/O at the edges.
⢠Each transaction will have its own,
autonomous, evolvableTransaction Script
136. In conclusionâŚ
Transaction scripts should be more popular
⢠Business-focused not technology-focused
⢠Great for agile:
â "Transaction" as the unit of development
⢠Easy to understand: dataflow is one directional
⢠Less bloat. You get ISP andYAGNI for free!
137. In conclusionâŚ
Problems are solved by
⢠FP composability
⢠Separation of I/O
New, improved transaction scripts!
⢠Have a rich domain
⢠Are easy to modify
⢠Are easy to test
⢠Are microservice and serverless friendly!
138. "Reinventing theTransaction Script"
â Slides and video will be posted at
⢠fsharpforfunandprofit.com/transactionscript
Related talks
â "The Power of Composition"
⢠fsharpforfunandprofit.com/composition
â "Domain Modeling Made Functional"
⢠fsharpforfunandprofit.com/ddd
Thanks!
Twitter: @ScottWlaschin