Ce diaporama a bien été signalé.
Le téléchargement de votre SlideShare est en cours. ×

Reinventing the Transaction Script (NDC London 2020)

Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité
Publicité

Consultez-les par la suite

1 sur 138 Publicité

Reinventing the Transaction Script (NDC London 2020)

Télécharger pour lire hors ligne

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.

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.

Publicité
Publicité

Plus De Contenu Connexe

Diaporamas pour vous (20)

Similaire à Reinventing the Transaction Script (NDC London 2020) (20)

Publicité

Plus par Scott Wlaschin (20)

Plus récents (20)

Publicité

Reinventing the Transaction Script (NDC London 2020)

  1. 1. Reinventing the Transaction Script (NDC London 2020) @ScottWlaschin fsharpforfunandprofit.com
  2. 2. Are transaction scripts a Bad Thing?
  3. 3. Are transaction scripts a Bad Thing? No!
  4. 4. Part I What is a "Transaction Script"?
  5. 5. What is a "Transaction Script"?
  6. 6. Question: How should you organize your domain logic?
  7. 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. 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. 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!
  10. 10. The developer's curse Simple Complex
  11. 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!
  12. 12. The pros and cons of Transaction Scripts
  13. 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. 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. 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. 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. 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. 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. 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!
  20. 20. The fix: Use Functional Programming & Domain Driven Design High coolness score
  21. 21. Part II Intro to Functional Programming Function
  22. 22. The Tunnel of Transformation Function apple -> banana A function is a thing which transforms inputs to outputs
  23. 23. A function is a standalone thing, not attached to a class It can be used for inputs and outputs of other functions
  24. 24. input A function can be an output A function is a standalone thing
  25. 25. output A function can be an input A function is a standalone thing
  26. 26. input output A function can be a parameter A function is a standalone thing
  27. 27. Core FP principle: Composition everywhere
  28. 28. What is Composition?
  29. 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
  30. 30. All pieces are designed to be connected
  31. 31. The pieces are reusable in different contexts
  32. 32. Connect two pieces together and get another "piece" that can still be connected
  33. 33. Make big things from small things in the same way
  34. 34. Function Composition
  35. 35. Function composition Function 1 apple -> banana Function 2 banana -> cherry
  36. 36. Function composition >> Function 1 apple -> banana Function 2 banana -> cherry
  37. 37. Function composition New Function apple -> cherry Can't tell it was built from smaller functions! Where did the banana go? (abstraction)
  38. 38. Building big things from functions It's compositions all the way up
  39. 39. Low-level operation ToUpper stringstring
  40. 40. Low-level operation Service AddressValidator Validation Result Address Low-level operation Low-level operation
  41. 41. Service Use-case UpdateProfileData ChangeProfile Result ChangeProfile Request Service Service
  42. 42. Use-case Web application Http Response Http Request Use-case Use-case
  43. 43. Http Response Http Request Check out my "Power of Composition" talk for more details.
  44. 44. Composition everywhere: Types can be composed too
  45. 45. Algebraic type system
  46. 46. New types are built from smaller types by: Composing with “AND” Composing with “OR”
  47. 47. Example: pairs, tuples, records FruitSalad = One each of and and Compose with “AND” type FruitSalad = { Apple: AppleVariety Banana: BananaVariety Cherry: CherryVariety }
  48. 48. Snack = or or Compose with “OR” type Snack = | Apple of AppleVariety | Banana of BananaVariety | Cherry of CherryVariety
  49. 49. Real world example of type composition
  50. 50. 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
  51. 51. 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:
  52. 52. type EmailAddress = string type CardNumber = string In FP you would probably implement by composing types, like this:
  53. 53. type EmailAddress = ... type CardNumber = … type CardType = Visa | Mastercard type CreditCardInfo = { CardType : CardType CardNumber : CardNumber }
  54. 54. type EmailAddress = ... type CardNumber = ... type CardType = ... type CreditCardInfo = ... type PaymentMethod = | Cash | PayPal of EmailAddress | Card of CreditCardInfo
  55. 55. type EmailAddress = ... type CardNumber = ... type CardType = ... type CreditCardInfo = ... type PaymentMethod = | Cash | PayPal of EmailAddress | Card of CreditCardInfo type PaymentAmount = decimal type Currency = EUR | USD
  56. 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 type Payment = { Amount : PaymentAmount Currency: Currency Method: PaymentMethod }
  57. 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. 58. Applying FP principles to Transaction Scripts
  59. 59. Introducing "Workflows" A.k.a. "Transaction", "Story" , "Use-case" Check out "Event Storming" for understanding business events
  60. 60. Implementing workflows A workflow will be implemented by a function!
  61. 61. Composable Type Composable Type Implementing workflows Inputs and outputs are defined by composable types
  62. 62. Implementing workflows We will compose a workflow from smaller components Each component is a standalone function
  63. 63. Part III Intro to Domain-Driven Design
  64. 64. The DDD book came out just after PEAA in 2003 "Focus on the domain and domain logic rather than technology" -- Eric Evans
  65. 65. Agile
  66. 66. Agile
  67. 67. Domain-Driven Design
  68. 68. Domain-Driven Design
  69. 69. • 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?
  70. 70. Key DDD principle: Communicate the design in the code
  71. 71. Can you really make code represent the domain?
  72. 72. What some source code looks like
  73. 73. 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#)
  74. 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 * means a pair. Choose one from each type list type is built in
  75. 75. Deal (original) Deck (remaining) Deck (on table) Card Modeling a workflow with a function type Deal = Deck -> (Deck * Card) Input Output
  76. 76. Pickup Card (updated) Hand (original) Hand (on table) Card Modeling a workflow with a function type PickupCard = (Hand * Card) –> Hand Input Output
  77. 77. 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
  78. 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. 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 Can non-programmers provide useful feedback?
  80. 80. 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
  81. 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. 82. In the real world Suit Rank Card Hand Deck Player Deal In the code Suit Rank Card Hand Deck Player Deal
  83. 83. In the real world Suit Rank Card Hand Deck Player Deal In the code Suit Rank Card Hand Deck Player Deal PlayerController DeckBase AbstractCardProxyFactoryBean 
  84. 84. 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
  85. 85. Key DDD principle: Communicate the design in the code 
  86. 86. Introducing "bounded contexts"
  87. 87. Grouping functionality in the domain A bounded context
  88. 88. 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!
  89. 89. Bounded context
  90. 90. Bounded context with workflows A bounded context contains a set of related workflows
  91. 91. Bounded context with workflows A bounded context contains a set of related workflows/functions
  92. 92. Part IV Reinventing the Transaction Script
  93. 93. • 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
  94. 94. 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
  95. 95. Claim: FP-style transaction scripts are easier to comprehend than OO domain models.
  96. 96. FP workflow All arrows go left to right
  97. 97. Example: a web backend One directional flow, even with branching
  98. 98. Object Oriented workflow Arrows go in all directions We don't design microservices this way!
  99. 99. Real-world OO dependency graph Multiple cyclic dependencies 
  100. 100. Real-world FP dependency graph All arrows go left to right  Same functionality as the OO example
  101. 101. Claim: FP-style transaction scripts are resistant to bloat
  102. 102. Some OO-style interfaces interface IRepository { Insert InsertAsync Delete DeleteAsync Update UpdateAsync CommitChanges CommitChangesAsync Query QueryByKey QueryWithFilter QueryWithSpecification Contains Count QuerySummary QuerySummaryV2 ChangePassword ChangePasswordV2 DeleteAllRows LaunchMissiles LaunchMissilesV2 } One interface that supports *every* possible workflow! Where's the Interface segregation principle?
  103. 103. FP workflow Transaction script style functions only contain what they need. Every part is relevant. You get the ISP for free.
  104. 104. Claim: FP-style transaction scripts can be modified with confidence
  105. 105. Modifying a workflow Replace a component Static type checking ensures that sub-components are used correctly Minimizing the amount of code that I touch
  106. 106. Add features to a workflow Insert new logic Minimizing the amount of code that I touch
  107. 107. Add branching to a workflow Add conditional logic
  108. 108. Parameterizing a workflow for reuse Swap out parameters for reuse as long as input and output types match
  109. 109. Claim: FP-style transaction scripts can have a rich domain model
  110. 110. 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
  111. 111. Sharing rich domain components Shared component Yes, complex domain logic *is* compatible with transaction scripts
  112. 112. Claim: FP-style transaction scripts are the natural evolution of Onion/Clean/Hexagonal Architecture
  113. 113. Traditional layered model A change to the way that a workflow works means that you need to touch every layer.
  114. 114. 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.
  115. 115. Vertical slices stretched out Confusing! 
  116. 116. The "onion" architecture Core domain is pure, and all I/O is at the edges  See "Functional Core/Imperative Shell"
  117. 117. Claim: FP-style transaction scripts are easy to test
  118. 118. 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!
  119. 119. In a functional design, all I/O is at the edges. A FP-style transaction script
  120. 120. Database Database Load data Process it Save it
  121. 121. DeterministicNon-deterministic Non-deterministic
  122. 122. This makes testing easy!
  123. 123. I/O in the middle of a workflow Keep them separate
  124. 124. FP style transactions work on the front end too! event-driven!
  125. 125. MVU is a FP style approach As seen in Elm, Redux, etc
  126. 126. MVU is a FP style approach Deterministic function Deterministic function Non-deterministic actions ("I/O")
  127. 127. PartV Deployment options for FP-style transaction scripts
  128. 128. 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
  129. 129. In conclusion: The ReinventedTransaction Script
  130. 130. 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
  131. 131. 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!
  132. 132. 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!
  133. 133. "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

×