Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

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

183 views

Published on

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#.

Published in: Education
  • Be the first to comment

  • Be the first to like this

.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

×