Successfully reported this slideshow.
Your SlideShare is downloading. ×

.NET Fest 2017. Mark Seemann. From Dependency injection to dependency rejection

.NET Fest 2017. Mark Seemann. From Dependency injection to dependency rejection

In object-oriented design, dependency injection is a well-known design pattern, although it's a complicated solution to the problem of decoupling. Functional programming offers a simpler way. This talk examines dependency injection in object-oriented design, and explains how it's not required (nor desired) in functional programming. You don't need to know Haskell or F# to attend this session; relevant syntax will be explained just-in-time. Object-oriented examples will be in C#.

In object-oriented design, dependency injection is a well-known design pattern, although it's a complicated solution to the problem of decoupling. Functional programming offers a simpler way. This talk examines dependency injection in object-oriented design, and explains how it's not required (nor desired) in functional programming. You don't need to know Haskell or F# to attend this session; relevant syntax will be explained just-in-time. Object-oriented examples will be in C#.

More Related Content

More from NETFest

Related Books

Free with a 30 day trial from Scribd

See all

.NET Fest 2017. Mark Seemann. From Dependency injection to dependency rejection

  1. 1. From dependency injection to dependency rejection Mark Seemann http://blog.ploeh.dk @ploeh
  2. 2. @ploeh
  3. 3. Object-oriented Functional @ploeh
  4. 4. Object-oriented Functional Dependency injection Partial application Composition @ploeh
  5. 5. Dependency injection is “really just a pretentious way to say ‘taking an argument’” - Rúnar Bjarnason (@runarorama) @ploeh
  6. 6. Example Restaurant Reservation @ploeh
  7. 7. Restaurant Reservation Make a reservation Date Name Email Quantity Submit @ploeh
  8. 8. Restaurant Reservation Make a reservation Date 2 Name Email Quantity Submit @ploeh
  9. 9. Restaurant Reservation Make a reservation Date 20 Name Email Quantity Submit @ploeh
  10. 10. Restaurant Reservation Make a reservation Date 201 Name Email Quantity Submit @ploeh
  11. 11. Restaurant Reservation Make a reservation Date 2016 Name Email Quantity Submit @ploeh
  12. 12. Restaurant Reservation Make a reservation Date 2016- Name Email Quantity Submit @ploeh
  13. 13. Restaurant Reservation Make a reservation Date 2016-1 Name Email Quantity Submit @ploeh
  14. 14. Restaurant Reservation Make a reservation Date 2016-12 Name Email Quantity Submit @ploeh
  15. 15. Restaurant Reservation Make a reservation Date 2016-12- Name Email Quantity Submit @ploeh
  16. 16. Restaurant Reservation Make a reservation Date 2016-12-2 Name Email Quantity Submit @ploeh
  17. 17. Restaurant Reservation Make a reservation Date 2016-12-29 Name Email Quantity Submit @ploeh
  18. 18. Restaurant Reservation Make a reservation Date 2016-12-29 Name M Email Quantity Submit @ploeh
  19. 19. Restaurant Reservation Make a reservation Date 2016-12-29 Name Ma Email Quantity Submit @ploeh
  20. 20. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mar Email Quantity Submit @ploeh
  21. 21. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Email Quantity Submit @ploeh
  22. 22. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Email Quantity Submit @ploeh
  23. 23. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark S Email Quantity Submit @ploeh
  24. 24. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Se Email Quantity Submit @ploeh
  25. 25. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark See Email Quantity Submit @ploeh
  26. 26. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Seem Email Quantity Submit @ploeh
  27. 27. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Seema Email Quantity Submit @ploeh
  28. 28. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Seeman Email Quantity Submit @ploeh
  29. 29. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Seemann Email Quantity Submit @ploeh
  30. 30. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Seemann Email ma Quantity Submit @ploeh
  31. 31. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Seemann Email mar Quantity Submit @ploeh
  32. 32. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Seemann Email mark Quantity Submit @ploeh
  33. 33. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Seemann Email mark@ Quantity Submit @ploeh
  34. 34. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Seemann Email mark@p Quantity Submit @ploeh
  35. 35. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Seemann Email mark@pl Quantity Submit @ploeh
  36. 36. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Seemann Email mark@plo Quantity Submit @ploeh
  37. 37. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Seemann Email mark@ploe Quantity Submit @ploeh
  38. 38. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Seemann Email mark@ploeh Quantity Submit @ploeh
  39. 39. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Seemann Email mark@ploeh. Quantity Submit @ploeh
  40. 40. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Seemann Email mark@ploeh.d Quantity Submit @ploeh
  41. 41. Restaurant Reservation Make a reservation Date 2016-12-29 Name Mark Seemann Email mark@ploeh.dk Quantity Submit @ploeh
  42. 42. Restaurant Reservation POST JSON @ploeh Make a reservation Date 2016-12-29 Name Mark Seemann Email mark@ploeh.dk Quantity 4 Submit
  43. 43. Restaurant Reservation POST JSON @ploeh Make a reservation Date 2016-12-29 Name Mark Seemann Email mark@ploeh.dk Quantity 4 Submit
  44. 44. Validate Get reserved seats from DB Decide Save reservation Create response @ploeh
  45. 45. Object-oriented Dependency injection @ploeh
  46. 46. public class ReservationsController : ApiController { public ReservationsController(IValidator validator, IMapper mapper, IMaîtreD maîtreD) { this.validator = validator; this.mapper = mapper; this.maîtreD = maîtreD; } public IHttpActionResult Post(ReservationRequestDto dto) { var validationMsg = validator.Validate(dto); if (validationMsg != "") return this.BadRequest(validationMsg); var r = mapper.Map(dto); var id = maîtreD.TryAccept(r); if (id == null) return this.StatusCode(HttpStatusCode.Forbidden); return this.Ok(); } } POST /reservations HTTP/1.1 { "date": "2016-12-08", "name": "Mark Seemann", "email": "mark@ploeh.dk", "quantity": 4 } @ploeh
  47. 47. public class MaîtreD : IMaîtreD { public MaîtreD(int capacity, IReservationsRepository reservationsRepository) { this.capacity = capacity; this.reservationsRepository = reservationsRepository; } public int? TryAccept(Reservation reservation) { var reservedSeats = reservationsRepository .ReadReservations(reservation.Date) .Sum(r => r.Quantity); if (reservedSeats + reservation.Quantity <= capacity) { reservation.IsAccepted = true; return reservationsRepository.Create(reservation); } return null; } } @ploeh public interface IMaîtreD { int? TryAccept(Reservation reservation); }
  48. 48. How do I do dependency injection in Scala? You don’t, because Scala is a functional language. Comic courtesy of @jrecursive and @hmemcpy @ploeh
  49. 49. Fine, it’s functional. How do I inject dependencies? You use a Free Monad, which allows you to build a Monad from any Functor. Comic courtesy of @jrecursive and @hmemcpy @ploeh
  50. 50. Did you just tell me to go fuck myself? I believe I did, Bob. Comic courtesy of @jrecursive and @hmemcpy @ploeh
  51. 51. Partial application An attempt at dependency injection with a functional language @ploeh
  52. 52. // int -> (DateTimeOffset -> Reservation list) -> (Reservation -> int) -> Reservation // -> int option let tryAccept capacity readReservations createReservation reservation = let reservedSeats = readReservations reservation.Date |> List.sumBy (fun x -> x.Quantity) if reservedSeats + reservation.Quantity <= capacity then createReservation { reservation with IsAccepted = true } |> Some else None @ploeh Repository
  53. 53. // Reservation -> int option let tryAcceptComposition reservation = let read = DB.readReservations connectionString @ploeh string -> DateTimeOffset -> Reservation list
  54. 54. // Reservation -> int option let tryAcceptComposition reservation = let read = DB.readReservations connectionString @ploeh string -> DateTimeOffset -> Reservation list connectionString
  55. 55. // Reservation -> int option let tryAcceptComposition reservation = let read = DB.readReservations connectionString @ploeh string -> DateTimeOffset -> Reservation listconnectionString
  56. 56. // Reservation -> int option let tryAcceptComposition reservation = let read = DB.readReservations connectionString @ploeh DateTimeOffset -> Reservation list
  57. 57. // Reservation -> int option let tryAcceptComposition reservation = let read = DB.readReservations connectionString let create = DB.createReservation connectionString tryAccept 10 read create reservation @ploeh Reservation -> int
  58. 58. // Reservation -> int option let tryAcceptComposition reservation = let read = DB.readReservations connectionString let create = DB.createReservation connectionString tryAccept 10 read create reservation @ploeh
  59. 59. // Reservation -> int option let tryAcceptComposition = let read = DB.readReservations connectionString let create = DB.createReservation connectionString tryAccept 10 read create @ploeh
  60. 60. // Reservation -> int option let tryAcceptComposition = let read = DB.readReservations connectionString let create = DB.createReservation connectionString tryAccept 10 read create @ploeh
  61. 61. IL @ploeh
  62. 62. internal class tryAcceptComposition@17 : FSharpFunc<Reservation, FSharpOption<int>> { public int capacity; public FSharpFunc<Reservation, int> createReservation; public FSharpFunc<DateTimeOffset, FSharpList<Reservation>> readReservations; internal tryAcceptComposition@17( int capacity, FSharpFunc<DateTimeOffset, FSharpList<Reservation>> readReservations, FSharpFunc<Reservation, int> createReservation) { this.capacity = capacity; this.readReservations = readReservations; this.createReservation = createReservation; } public override FSharpOption<int> Invoke(Reservation reservation) { return MaîtreD.tryAccept<int>( this.capacity, this.readReservations, this.createReservation, reservation); } } @ploeh
  63. 63. Is it Functional? Partial application Dependency injection @ploeh
  64. 64. Same return value for same input No side- effects Pure function @ploeh
  65. 65. Pure function Impure function @ploeh
  66. 66. Sanity check @ploeh
  67. 67. tryAccept :: Int -> (ZonedTime -> [Reservation]) -> (Reservation -> Int) -> Reservation -> Maybe Int tryAccept capacity readReservations createReservation reservation = let reservedSeats = sum $ map quantity $ readReservations $ date reservation in if reservedSeats + quantity reservation <= capacity then Just $ createReservation $ reservation { isAccepted = True } else Nothing @ploeh Pure
  68. 68. tryAcceptComposition :: Reservation -> IO (Maybe Int) tryAcceptComposition reservation = let read = DB.readReservations connectionString create = DB.createReservation connectionString in tryAccept 10 read create reservation @ploeh ZonedTime -> IO [Reservation] Reservation -> IO Int ZonedTime -> [Reservation] Reservation -> Int
  69. 69. Not Functional Partial application Dependency injection @ploeh
  70. 70. @ploeh
  71. 71. Dependency injection makes everything impure @ploeh
  72. 72. Dependency Rejection @ploeh
  73. 73. Unit Dependencies Indirect input Indirect output Direct input Direct output @ploeh
  74. 74. Unit Dependencies Indirect input Direct input Direct output @ploeh
  75. 75. // int -> (DateTimeOffset -> Reservation list) -> (Reservation -> int) -> Reservation // -> int option let tryAccept capacity readReservations createReservation reservation = @ploeh
  76. 76. // int -> (DateTimeOffset -> Reservation list) -> Reservation // -> Reservation option let tryAccept capacity readReservations reservation = @ploeh
  77. 77. // int -> (DateTimeOffset -> Reservation list) -> Reservation // -> Reservation option let tryAccept capacity readReservations reservation = @ploeh
  78. 78. // int -> (DateTimeOffset -> Reservation list) -> Reservation -> Reservation option let tryAccept capacity readReservations reservation = @ploeh
  79. 79. // int -> (DateTimeOffset -> Reservation list) -> Reservation -> Reservation option let tryAccept capacity readReservations reservation = let reservedSeats = readReservations reservation.Date |> List.sumBy (fun x -> x.Quantity) if reservedSeats + reservation.Quantity <= capacity then { reservation with IsAccepted = true } |> Some else None @ploeh
  80. 80. // Reservation -> int option let tryAcceptComposition reservation = match tryAccept 10 (DB.readReservations connectionString) reservation with | None -> None | Some r -> (DB.createReservation connectionString) r |> Some @ploeh
  81. 81. // Reservation -> int option let tryAcceptComposition reservation = tryAccept 10 (DB.readReservations connectionString) reservation |> Option.map (fun r -> DB.createReservation connectionString r) @ploeh
  82. 82. // Reservation -> int option let tryAcceptComposition reservation = tryAccept 10 (DB.readReservations connectionString) reservation |> Option.map ( DB.createReservation connectionString ) @ploeh
  83. 83. // Reservation -> int option let tryAcceptComposition reservation = tryAccept 10 (DB.readReservations connectionString) reservation |> Option.map (DB.createReservation connectionString) @ploeh
  84. 84. // Reservation -> int option let tryAcceptComposition reservation = reservation |> tryAccept 10 (DB.readReservations connectionString) |> Option.map (DB.createReservation connectionString) @ploeh
  85. 85. Unit Dependencies Indirect input Direct input Direct output @ploeh
  86. 86. Unit Dependencies Direct input Direct output @ploeh
  87. 87. // int -> (DateTimeOffset -> Reservation list) -> Reservation -> Reservation option let tryAccept capacity readReservations reservation = @ploeh
  88. 88. // int -> ( Reservation list) -> Reservation -> Reservation option let tryAccept capacity readReservations reservation = @ploeh
  89. 89. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity readReservations reservation = @ploeh
  90. 90. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity radReservations reservation = @ploeh
  91. 91. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity rdReservations reservation = @ploeh
  92. 92. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity rReservations reservation = @ploeh
  93. 93. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity reservations reservation = @ploeh
  94. 94. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity reservations reservation = @ploeh
  95. 95. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity reservations reservation = @ploeh
  96. 96. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity reservations reservation = @ploeh
  97. 97. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity reservations reservation = @ploeh
  98. 98. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity reservations reservation = @ploeh
  99. 99. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity reservations reservation = @ploeh
  100. 100. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity reservations reservation = @ploeh
  101. 101. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity reservations reservation = @ploeh
  102. 102. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity reservations reservation = @ploeh
  103. 103. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity reservations reservation = @ploeh
  104. 104. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity reservations reservation = @ploeh
  105. 105. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity reservations reservation = @ploeh
  106. 106. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity reservations reservation = @ploeh
  107. 107. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity reservations reservation = @ploeh
  108. 108. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity reservations reservation = @ploeh
  109. 109. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity reservations reservation = let reservedSeats = reservations |> List.sumBy (fun x -> x.Quantity) if reservedSeats + reservation.Quantity <= capacity then { reservation with IsAccepted = true } |> Some else None @ploeh
  110. 110. // int -> Reservation list -> Reservation -> Reservation option let tryAccept capacity reservations reservation = let reservedSeats = reservations |> List.sumBy (fun x -> x.Quantity) if reservedSeats + reservation.Quantity <= capacity then { reservation with IsAccepted = true } |> Some else None @ploeh
  111. 111. // ('a -> 'b -> 'c) -> 'b -> 'a -> 'c let flip f x y = f y x // Reservation -> int option let tryAcceptComposition reservation = reservation.Date |> DB.readReservations connectionString |> fun reservations -> tryAccept 10 reservations reservation |> Option.map (DB.createReservation connectionString) @ploeh
  112. 112. // ('a -> 'b -> 'c) -> 'b -> 'a -> 'c let flip f x y = f y x // Reservation -> int option let tryAcceptComposition reservation = reservation.Date |> DB.readReservations connectionString |> fun reservations -> flip (tryAccept 10) reservation reservations |> Option.map (DB.createReservation connectionString) @ploeh
  113. 113. // ('a -> 'b -> 'c) -> 'b -> 'a -> 'c let flip f x y = f y x // Reservation -> int option let tryAcceptComposition reservation = reservation.Date |> DB.readReservations connectionString |> -> flip (tryAccept 10) reservation |> Option.map (DB.createReservation connectionString) @ploeh
  114. 114. // ('a -> 'b -> 'c) -> 'b -> 'a -> 'c let flip f x y = f y x // Reservation -> int option let tryAcceptComposition reservation = reservation.Date |> DB.readReservations connectionString |> flip (tryAccept 10) reservation |> Option.map (DB.createReservation connectionString) @ploeh
  115. 115. Sanity check @ploeh
  116. 116. tryAccept :: Int -> [Reservation] -> Reservation -> Maybe Reservation tryAccept capacity reservations reservation = let reservedSeats = sum $ map quantity reservations in if reservedSeats + quantity reservation <= capacity then Just $ reservation { isAccepted = True } else Nothing @ploeh
  117. 117. tryAcceptComposition :: Reservation -> IO (Maybe Int) tryAcceptComposition reservation = runMaybeT $ liftIO (DB.readReservations connectionString $ date reservation) >>= MaybeT . return . flip (tryAccept 10) reservation >>= liftIO . DB.createReservation connectionString @ploeh
  118. 118. Object-oriented Functional Dependency injection Partial application Composition @ploeh
  119. 119. Mark Seemann http://blog.ploeh.dk http://bit.ly/ploehralsight @ploeh

×