Successfully reported this slideshow.
Your SlideShare is downloading. ×

Responsible DI: Ditch the Frameworks

Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Ad
Loading in …3
×

Check these out next

1 of 99 Ad

Responsible DI: Ditch the Frameworks

Download to read offline

Framework-driven dependency injection, as practiced by many OO programmers, tends to have considerable and underappreciated drawbacks. This talk goes into detail about why.

Framework-driven dependency injection, as practiced by many OO programmers, tends to have considerable and underappreciated drawbacks. This talk goes into detail about why.

Advertisement
Advertisement

More Related Content

Similar to Responsible DI: Ditch the Frameworks (20)

Advertisement

Recently uploaded (20)

Advertisement

Responsible DI: Ditch the Frameworks

  1. 1. Responsible DI (Ditch the frameworks!) @KenScambler MYOB
  2. 2. Spring / Guice / Dagger 2
  3. 3. Problems with DI frameworks • Cognitive overheard, confusing semantics • Smear application-level responsibility everywhere • Makes it too easy to not think about responsibilities • Makes it harder to reason about code in isolation • Throws tech at a problem that shouldn’t exist
  4. 4. What problems do DI frameworks solve • “Factories aren’t fun” • “Think of Guice’s @Inject as the new new” Guice Dagger 2 • “App assembly can be 100KLOC” • ”Replace FactoryFactory classes” • “Swapping dependencies for testing” Spring • “Initialisation ordering”
  5. 5. ApplicationContext, BeanFactory, container, beans, configuration metadata, BeanDefinitionReader, autowiring Spring Guice @Inject, module, AbstractModule, injector, scopes, providers, linked bindings, instance bindings, provider bindings, constructor bindings, untargetted bindings, built-in bindings, scopes, injection points Dagger 2 module, provider, @Inject, @Provides, bindings, component, Lazy, injector, builder, code gen, qualifier, component provision methods, @Reusable scope
  6. 6. Actual problem Need to pass arguments to things Actual solution Pass arguments to things
  7. 7. More constructively: Actual problem • Of the arguments we need to pass, some are selected once for the application and never changed. It would be bad to select them over and over again Actual solution • Only one place should know that it is in an ”application” • Wire up the things that never change here • Use good software engineering to reduce the number of things
  8. 8. 1. Dependencies from scratch
  9. 9. Case study – Video Rental API Video Search Controller Payment Controller Video Searcher Payments Payments Gateway Rental Controller Rentals Video Repo Video DB Analytics Logging 3rd Party
  10. 10. What if there were no objects? handleVideoSearch Request handlePayment Request findByName findSimilar charge transferFrom Account handleRental Request rentVideo runVideoQuery record Debug, warn, error
  11. 11. public class Rentals { public static Receipt rentVideo(Video v, VideoStore store, Customer c) { store.decrementStock(vid); Payments.charge(c.getCreditCard(), v.getPrice()); List<Movie> recommendations = VideoSearcher.findSimilar(v); Logger.debug(LogMessages.VIDEO_RENTED); return new Receipt(v.getId(), c.getId(), recommendations); } }
  12. 12. public class Rentals { public static Receipt rentVideo(Video v, VideoStore store, Customer c) { store.decrementStock(vid); Payments.charge(c.getCreditCard(), v.getPrice()); List<Movie> recommendations = VideoSearcher.findSimilar(v); Logger.debug(LogMessages.VIDEO_RENTED); return new Receipt(v.getId(), c.getId(), recommendations); } }
  13. 13. handleVideoSearch Request handlePayment Request findByName charge transferFrom Account handleRental Request rentVideo runVideoQuery(DB) record Debug, warn, error findSimilar I need a DB!
  14. 14. handleVideoSearch Request handlePayment Request charge transferFrom Account handleRental Request rentVideo runVideoQuery(DB) record Debug, warn, error findByName (DB) findSimilar (DB)
  15. 15. handlePayment Request charge handleRental Request runVideoQuery(DB) record Debug, warn, error findByName (DB) findSimilar (DB) handleVideoSearch Request (DB) rentVideo (DB) transferFrom Account
  16. 16. handlePayment Request charge transferFrom Account runVideoQuery(DB) record Debug, warn, error findByName (DB) findSimilar (DB) handleVideoSearch Request (DB) rentVideo (DB) handleRental Request (DB)
  17. 17. handlePayment Request charge runVideoQuery(DB) record Debug, warn, error findByName (DB) findSimilar (DB) handleVideoSearch Request (DB) rentVideo (DB) handleRental Request (DB) transferFrom Account(PG) I need a payment gateway!
  18. 18. handlePayment Request charge(PG) runVideoQuery(DB) record Debug, warn, error findByName (DB) findSimilar (DB) handleVideoSearch Request (DB) rentVideo (DB) handleRental Request (DB) transferFrom Account(PG)
  19. 19. charge(PG) runVideoQuery(DB) record Debug, warn, error findByName (DB) findSimilar (DB) handleVideoSearch Request (DB) rentVideo (DB, PG) handleRental Request (DB) transferFrom Account(PG) handlePayment Request (PG)
  20. 20. handleVideoSearch Request (DB) handlePayment Request (PG) findByName (DB) charge(PG) transferFrom Account(PG) handleRental Request (DB, PG) rentVideo (DB, PG) runVideoQuery(DB) record Debug, warn, error findSimilar (DB)
  21. 21. Trickling up is very, very bad 1. Punches through abstraction layers 2. Changes ripple 3. Same arguments must be supplied many times to many places, but for only one reason
  22. 22. Objects = 1 handleVideoSearch Request handlePayment Request charge transferFrom Account handleRental Request rentVideo record Debug, warn, error Video Repo Video DB findByName findSimilar
  23. 23. Objects = 2 handleVideoSearch Request handlePayment Request charge transferFrom Account handleRental Request rentVideo record Debug, warn, error Video Repo Video DB Video Searcher
  24. 24. Objects = 4 handlePayment Request charge transferFrom Account handleRental Request record Debug, warn, error Video Repo Video DB Video Searcher Rentals Video Search Controller
  25. 25. Objects = 5 handlePayment Request charge transferFrom Account record Debug, warn, error Video Repo Video DB Video Searcher Rentals Video Search Controller Rental Controller
  26. 26. Objects = 7 handlePayment Request charge transferFrom Account Video Repo Video DB Video Searcher Rentals Video Search Controller Rental Controller Analytics Logging
  27. 27. Objects = 8 handlePayment Request charge Video Repo Video DB Video Searcher Rentals Video Search Controller Rental Controller Analytics Logging Payments Gateway 3rd Party
  28. 28. Objects = 9 handlePayment Request Video Repo Video DB Video Searcher Rentals Video Search Controller Rental Controller Analytics Logging Payments Gateway 3rd Party Payments
  29. 29. Objects = 10 Video Repo Video DB Video Searcher Rentals Video Search Controller Rental Controller Analytics Logging Payments Gateway 3rd Party Payments Payment Controller
  30. 30. Object creep is real! …but isn’t that ok? We’re writing OO software, after all
  31. 31. Video Searcher Rentals Logging Payments
  32. 32. public class Rentals { private final VideoSearcher search; private final Payments p; private final Logger log; // Obvious constructor public Receipt rentVideo(Video v, VideoStore store, Customer c) { store.decrementStock(vid); p.charge(c.getCreditCard(), v.getPrice()); List<Movie> recommendations = search.findSimilar(v); log.debug(LogMessages.VIDEO_RENTED); return new Receipt(v.getId(), c.getId(), recommendations); } }
  33. 33. Rentals - rentVideo “Coathanger object”: just exists to hang the functionality on
  34. 34. Rentals - rentVideo VideoSearcher Payments Logger
  35. 35. Rentals - rentVideo Payments Logger VideoSearcher VideoRepo - findSimilar
  36. 36. • Hiding functionality in objects isn’t free • Inhibits composition & reuse • Can we reduce the number of objects we use?
  37. 37. 2. Bleeding responsibilities
  38. 38. The place where the wiring happens var logger = LoggingFramework.newLogger(); var analytics = MediaBubbleMonkeyAnalytics.create(); var conn = new SpecificDatabaseConnection(connString); var videoRepo = new VideoRepo(conn); var videoSearcher = new VideoSearcher(videoRepo); var gateway = new PaymentsGateway(url); var payments = new Payments(gateway); var rentals = new Rentals(videoSearcher, payments); var videoSearchController = new VideoSearchController(videoSearcher); var rentalSearchController = new RentalSearchController(rentals); var paymentsController = new PaymentsController(payments);
  39. 39. Some people say “yeah, I can manage it myself”… but in an Android application, we had 3000 LOC. In a large server-side app, it flushes out to about 100k LOC. Greg Kick, Dagger 2 author (Google)
  40. 40. “I know I am in an application, and what it is for” var logger = LoggingFramework.newLogger(); var analytics = MediaBubbleMonkeyAnalytics.create(); var conn = new SpecificDatabaseConnection(connString); var videoRepo = new VideoRepo(conn); var videoSearcher = new VideoSearcher(videoRepo); var gateway = new PaymentsGateway(url); var payments = new Payments(gateway); var rentals = new Rentals(videoSearcher, payments); var videoSearchController = new VideoSearchController(videoSearcher); var rentalSearchController = new RentalSearchController(rentals); var paymentsController = new PaymentsController(payments);
  41. 41. DI frameworks help reduce code in that one spot ….but: “I know I’m in an application” is smooshed everywhere
  42. 42. public class Rentals { private final VideoSearcher search; private final Payments p; private final Logger log; public Rentals(VideoSearcher search, Payments p, Logger log) { this.search = search; this.p = p; this.log = log; } public Receipt rentVideo(Video v, VideoStore store, Customer c) { ... } }
  43. 43. public class Rentals { private final VideoSearcher search; private final Payments p; private final Logger log; public Rentals(VideoSearcher search, Payments p, Logger log) { this.search = search; this.p = p; this.log = log; } public Receipt rentVideo(Video v, VideoStore store, Customer c) { ... } } Who am I? What do I exist for?
  44. 44. What is the problem that makes me say “Aha! I’ll use Rentals to solve it” *** MOST IMPORTANT SLIDE IN THE WHOLE TALK *** Programmer
  45. 45. public class Rentals { ... public Receipt rentVideo(Video v, VideoStore store, Customer c) { ... } } Use me if you want to rent a video.
  46. 46. public class Rentals { ... public Receipt rentVideo(Video v, VideoStore store, Customer c) { ... } } What’s that you say, I’m in an “app”? I have no idea what this means
  47. 47. public class Rentals { private final VideoSearcher search; private final Payments p; private final Logger log; public Rentals(VideoSearcher search, Payments p, Logger log) { this.search = search; this.p = p; this.log = log; } public Receipt rentVideo(Video v, VideoStore store, Customer c) { ... } } I don’t care in the slightest which VideoSearcher, Payments or Logger we use
  48. 48. @Component(“Bob”) public class Rentals { private final VideoSearcher search; private final Payments p; private final Logger log; @Autowired public Rentals(VideoSearcher search, Payments p, Logger log) { this.search = search; this.p = p; this.log = log; } public Receipt rentVideo(Video v, VideoStore store, Customer c) { ... } } But…
  49. 49. @Component(“Bob”) public class Rentals { private final VideoSearcher search; private final Payments p; private final Logger log; @Autowired public Rentals(VideoSearcher search, Payments p, Logger log) { this.search = search; this.p = p; this.log = log; } public Receipt rentVideo(Video v, VideoStore store, Customer c) { ... } } There’s only one true Rentals. Create THE Rentals for the whole app to use. They can call it “Bob”.
  50. 50. @Component(“Bob”) public class Rentals { private final VideoSearcher search; private final Payments p; private final Logger log; @Autowired public Rentals(VideoSearcher search, Payments p, Logger log) { this.search = search; this.p = p; this.log = log; } public Receipt rentVideo(Video v, VideoStore store, Customer c) { ... } } I want THE VideoSearcher, THE Payments, and THE Log defined for the app.
  51. 51. interface VideoSearcher class LocalDbSearcher class InternetSearcher interface Payments class CreditCardPayments class BankPayments class HashMapSearcher
  52. 52. @Component(“Bob”) public class Rentals { private final VideoSearcher search; private final Payments p; private final Logger log; @Autowired public Rentals(@Qualifier(“internetSearch”) VideoSearcher search, @Qualifier(“creditPayments”) Payments p, @Qualifier(“log4J”) Logger log) { this.search = search; this.p = p; this.log = log; } public Receipt rentVideo(Video v, VideoStore store, Customer c) { ... } } I want a really really specific VideoSearcher, Payments, and Log that we get from a global register somewhere. I don’t care in the slightest which VideoSearcher, Payments or Logger we use ?????
  53. 53. public class Rentals { public Receipt rentVideo(Video v, VideoStore store, Customer c) { store.decrementStock(vid); p.charge(c.getCreditCard(), v.getRentalPrice()); List<Movie> recommendations = search.findSimilar(v); log.debug(LogMessages.VIDEO_RENTED); return new Receipt(v.getId(), c.getId(), recommendations); } } Use me if you want to rent a video. THEREFORE: 1. 2. 3. 4.
  54. 54. Test that we can rent a video: For some valid VideoSearcher, Payments, and Logger 1. Check the VideoStore got decremented 2. Check the Payments was charged 3. Check the logger received a LogMessages.VIDEO_RENTED message 4. Check our receipt has the expected data, including the expected recommended videos ALIGNS WITH OUR PURPOSE
  55. 55. public class RentalsTest { VideoSearcher search = new FixedResultsSearcher(...); Payments p = new InMemoryPayments(); InMemoryLogger log = new InMemoryLogger(); @Test public void stockGetsDecremented() { var rentals = new Rentals(search, p, log); var store = new InMemoryVideoStore(); var video = new Video(“Weekend at Bernies”, Price.cents(250)); rentals.rentVideo(video, store.put(video,2), new Customer(...)); assertEquals(1, store.count(video)); } ... }
  56. 56. public class Rentals { private final StuffThatHappensFirst firstStuff; ... @Autowired public Rentals(@Qualifier(“decrementAndPayStuff”) StuffThatHappensFirst firstStuff, VideoSearcher search, Logger log) { ... } public Receipt rentVideo(Video v, VideoStore store, Customer c) { firstStuff.doIt(store); List<Movie> recommendations = search.findSimilar(v); log.debug(“Video rented!”); return new Receipt(v.getId(), c.getId(), recommendations); } } Class responsibility has bled into the app!!!
  57. 57. 3. How to make less things that need wiring?
  58. 58. There is nothing wrong with “new”
  59. 59. 1 + 4 * 5
  60. 60. 1.add(4.mult(5))
  61. 61. new Adder().add(1, new Multiplier().mult(4,5))
  62. 62. class Multiplier { private final Adder adder = new Adder(); public int mult(int a, int b) { int tot = 0; for (int i = 0; i < a; i++) tot = adder.add(tot,b); return tot; } }
  63. 63. class Multiplier { private final Adder adder; public Multiplier(Adder a) { adder = a; } public int mult(int a, int b) { ... } }
  64. 64. class Multiplier { private final Adder adder = new Adder(); public int mult(int a, int b) { ... } } Who am I? What do I exist for?
  65. 65. class Multiplier { private final Adder adder = new Adder(); public int mult(int a, int b) { ... } } The point is: Multiply 2 ints into a resulting int Tool that helps us do our job
  66. 66. class Multiplier { private final Adder adder; public Multiplier(Adder a) { adder = a; } public int mult(int a, int b) { ... } } We do not care in the slightest how the ints get combined
  67. 67. class Multiplier { private final Adder adder; public Multiplier(Adder a) { adder = a; } public int mult(int a, int b) { ... } } I exist to … perform an action on ints a given number of times?
  68. 68. class GstPrice { private final Price untaxed; private final GstPolicy policy = new GstPolicy(); public GstPrice(Price untaxed) { this.untaxed = untaxed; } ... // arithmetic, rounding, display, etc }
  69. 69. class GstPrice { private final Price untaxed; private final GstPolicy policy = new GstPolicy(); public GstPrice(Price untaxed) { this.untaxed = untaxed; } ... // arithmetic, rounding, display, etc } I exist to provide a price that has provably had GST applied
  70. 70. class GstPrice { private final Price untaxed; private final GstPolicy policy = new GstPolicy(); public GstPrice(Price untaxed) { this.untaxed = untaxed; } ... // arithmetic, rounding, display, etc } This is a tool I need to do the job. GST is the point
  71. 71. class GstPrice { private final Price untaxed; private final GstPolicy policy = new GstPolicy(); public GstPrice(Price untaxed) { this.untaxed = untaxed; } ... // arithmetic, rounding, display, etc } I do not care in the slightest which untaxed price I am dealing with
  72. 72. class TaxedPrice { private final Price untaxed; private final TaxPolicy policy; public TaxedPrice(Price untaxed, TaxPolicy policy) { this.untaxed = untaxed; } ... // arithmetic, rounding, display, etc } I do not care in the slightest which untaxed price or tax policy I am dealing with
  73. 73. // Mostly we don’t want to vary the policy, so... class TaxedPriceFactory { private final TaxPolicy policy; // ...Obvious constructor public TaxedPrice newTaxedPrice(Price untaxed) { return new TaxedPrice(untaxed, policy); } }
  74. 74. • Don’t lazily turn every field into a dependency; it increases complexity and object creep. • Think hard about responsibilities. • Use new if it supports our reason for existing.
  75. 75. 4. Dependencies ain’t free
  76. 76. public class Rentals { private final VideoSearcher search; private final Payments p; private final Logger log; @Autowired public Rentals(VideoSearcher search, Payments p, Logger log) { ... } public Receipt rentVideo(Video v, VideoStore store, Customer c) { ... // Uses search, p & log } }
  77. 77. public class Rentals { private final VideoSearcher search; private final Payments p; private final Logger log; private final PopcornMachine p; @Autowired public Rentals(VideoSearcher search, Payments p, Logger log, PopcornMachine p) { ... } public Receipt rentVideo(Video v, VideoStore store, Customer c) { ... // Uses search, p & log } public Receipt rentVideoWithPopcornDeal(Video v, VideoStore store, Customer c) { ... } }
  78. 78. rentVideo
  79. 79. rentVideo rentVideoWithPopcornDeal
  80. 80. Getters and setters
  81. 81. • This is how a codebase goes to hell • Each dependency makes reuse, extension, reasoning & testing harder • Frameworks make it too easy to pretend it’s free
  82. 82. 5. “Setter injection” is malpractice
  83. 83. public class Rentals { private VideoSearcher search; private Payments p; private Logger log; public Rentals() {} public void setVideoSearcher(VideoSearcher v) {...} public void setPayments(Payments p) {...} public void setLogger(Logger log) {...} public Receipt rentVideo(Video v, VideoStore store, Customer c) {... } }
  84. 84. Programmer UsageConstruction - setVideoSearcher - setPayments - setLogger - rentVideo Totally different concerns!
  85. 85. Buy stuffSupply
  86. 86. Programmer Usage - rentVideointerface Rentals { Receipt rentVideo(...) }
  87. 87. • “Setter injection” is a bad idea • Public interfaces confuses usage & creation concerns • Nobody ever wants all that at once • Mutable, uncertain – what order did things happen in? • Frameworks make “setter injection” too easy & practical
  88. 88. 6. Making coathanger objects disappear
  89. 89. public class Payments { private final Gateway gateway; public Payments(Gateway gateway) {...} public Receipt charge(Price price) {...} }
  90. 90. public class Payments { public static Receipt charge(Price price, Gateway gateway) { ... } }
  91. 91. public class Payments { public static Function<Gateway, Receipt> charge(Price price) { ... } }
  92. 92. public class Payments { public static NeedsGateway<Receipt> charge(Price price) { ... } } Required flexibility is now built in to return type; no coathanger object required
  93. 93. charge(Price.cents(250)).flatMap( price -> doSomethingElse(price).map( result -> result.toString())); • “If only I had a Price, this is what I would totally do with it" • “Monadic style” • Popular with functional programmers • Maybe not so practical in Java
  94. 94. NeedsGateway<FinalResult> program; FinalResult result = program.actuallyRun(actualGateway);
  95. 95. Objects = 4 (just the IO points) handleVideoSearch Request handlePayment Request charge handleRental Request rentVideo Video Repo Video DB findByName findSimilar Gateway Analytics Logging The problem has vanished! There’s almost nothing left to wire
  96. 96. Conclusion • Passing arguments to things is not very hard • Wiring together applications is not very hard • Having a central “I am the application” place and thinking hard about responsibilities gives you what you want • DI frameworks are too complex, not necessary, and lead programmers and teams toward sloppy design habits.

×