Inversion of control in Java from naive to functional
2 Hello! My name is Marian Wamsiedel I am a Java developer You can find me at marian.wamsiedel@gmail.com
The inversion of control (IoC), or dependency injection is an older concept in the software development The standard way to implement IoC is by using a container and a dependency injection framework, like Spring The rise of microservices and the functional programming techniques offers new solutions and challenge the IoC implementation standards Motivation Since there is plenty information available on the web about IoC, the presentation does not cover the basics of IoC, it just focuses on IoC implementation The presentation contains a naive IoC implementation, a standard one and two functional IoC approaches As always, choose the right tool for the job! The code examples are available on github. They are sample implementations and should not be used in this form in a real life scenario 3
4 Inversion of control, the idea An object should receive its dependencies rather than create them This technique is largely used in Java enterprise applications, where services from the lower, or external layers are injected in objects from the upper, or internal layers Dependency injection allows clear separation between layers. The objects in the upper layers only need to know the contract that the services implement and not the concrete service implementation Dependency injection offers good support to unit testing. The tests can inject mocks, or test doubles, instead of real service implementations
5 Inversion of control, example The concepts can be illustrated by a simple application, that calculates a buy / sell / hold recommendation for a stock on the market The recommendation is emitted by a transaction logic class and takes into consideration more factors, like the company and company’s branch perspective, if the stock is currently overbought, or oversold and the market volatility All these factors are delivered by helper classes, that are normally injected into the high-level transaction logic class The code examples are available on github: https://github.com/mw1980/ondependencyinjection/
Inversion of control, approaches 6 ReaderCurryingIoC ContainerNaive
7 The naive approach walk alone and nobody will distract you
8 Naive approach, the idea Design your application with an “initialization” area. Initialize there all your services and all your high-level classes. Inject the service objects into the constructor of the high-level classes This area can be a class, like a service locator, if the application is small, or a collection of classes, if the application is bigger This way a pyramid of initialized objects ready to be used is created. When the application starts, pass the input parameters to the root object of this pyramid. The pyramid will be “alive” and will deliver the expected functionality https://github.com/mw1980/ondependencyinjection/ the ‘naive’ package
9 Naive approach, the code class ServiceLocator { static final TransactionLogic TRANSACTION_LOGIC = new NaiveTransactionLogic( new SampleStockAnalyst(), new SampleMarketAnalyst() ); public TransactionLogic transactionLogic() { return TRANSACTION_LOGIC; } } … //in the Main.java class public static void main(String[] args) { System.out.println( new ServiceLocator().transactionLogic().recommendedAction("BAY001")); } https://github.com/mw1980/ondependencyinjection/ the ‘naive’ package
10 Naive approach, considerations Advantages: - The code is decoupled from any external dependencies - There is no container magic involved Drawbacks: - The “initialization” area tends to get really ugly, if the application grows in complexity and the number of objects to be initialized is too big - Some other usual requirements are not considered, like: objects scoping (singleton, request, session …) https://github.com/mw1980/ondependencyinjection/ the ‘naive’ package
Inversion of control, approaches 11 ReaderCurryingIoC ContainerNaive
12 The Ioc container approach Put all your eggs in the same basket
13 Ioc container approach, the idea The standard approach to implement the inversion of control is to use a dependency injection framework and a container that stores all the objects that need to be injected The classes relevant for the dependency injection are configured in xml files, or coded in Java configuration files. The framework scans the configuration and populates the container with corresponding objects The dependency injection framework injects beans from the container into the new created objects, if the relevant classes contain some required annotations https://github.com/mw1980/ondependencyinjection/ the ‘guice’ package
14 Ioc container approach, the code public class GuiceModule extends AbstractModule { @Override protected void configure() { bind(StockAnalyst.class).to(SampleStockAnalyst.class); bind(MarketAnalyst.class).to(SampleMarketAnalyst.class); bind(TransactionLogic.class).to(GuiceTransactionLogic.class); } } ... public static void main(String[] args) { Injector injector = Guice.createInjector(new GuiceModule()); TransactionLogic transactionLogic = injector.getInstance(TransactionLogic.class); System.out.println(transactionLogic.recommendedAction("ABEA")); } https://github.com/mw1980/ondependencyinjection/ the ‘guice’ package
15 Ioc container approach, considerations Advantages: - This practice is already well known by the most developers, being the standard way to implement dependency injection - It is a comfortable solution to auto wire the project dependencies and quickly bootstrap any software project - The IoC frameworks also offer other functionality, like scoping Drawbacks: - The code is hard coupled on an external framework - Some form of container magic is involved: the overview is lost in a big project - It transforms compile time exceptions into the runtime exceptions, if the container configuration is invalid https://github.com/mw1980/ondependencyinjection/ the ‘guice’ package
Inversion of control, approaches 16 ReaderCurryingIoC ContainerNaive
17 The currying approach Use partial fixing to completely fix the problem
18 Currying approach, the idea See the situation through functional programming lens: Most of the application responsibilities can be seen as functions, that map some input to a result. The services that need to be injected are part of this function input Service instances can be injected as functions input parameters using partial fixing (currying). The function body contains only the business logic https://github.com/mw1980/ondependencyinjection/ the ‘functional’ package
19 Currying approach, the code class FunctionalModule { static final Function3<StockAnalyst, MarketAnalyst, String, Action> FULL_RECOMMENDATION = (stockAnalyst, marketAnalyst, stockId) -> Action.BUY; //real logic here to replace hard coded value static final Function<String, Action> SIMPLE_RECOMMENDATION = FULL_RECOMMENDATION .curried().apply(new SampleStockAnalyst()) .curried().apply(new SampleMarketAnalyst()); } ... public static void main(String[] args) { System.out.println(FunctionalModule.SIMPLE_RECOMMENDATION.apply("XYZ"));} https://github.com/mw1980/ondependencyinjection/ the ‘functional’ package
20 Advantages: - It is a simple solution, it does not require any dependency injection framework - There is no container magic involved - It is suitable for small projects, like microservices Drawbacks: - Standard java does not offer easy currying functionality, therefore the project needs some external library, like java slang (vavr) - There is no extra functionality provided, like scoping Currying approach, considerations https://github.com/mw1980/ondependencyinjection/ the ‘functional’ package
Inversion of control, approaches 21 ReaderCurryingIoC ContainerNaive
22 The reader pattern approach Functional programming, bring on the big guns
23 The reader is a container around a function: Reader<A, B> wraps around f : A -> B The reader allows the extraction of the value (of type B), by executing the wrapped function. It is also possible to compose the original function with a second one g: B -> C and receive a new reader container as result: MyReader[f : A -> B].map(g : B -> C) = NewReader[h: A -> C] Instead of injecting a service with one method, wrap the method logic in a function and create a reader around it. Use reader mapping functionality to implement the logic needed to calculate the expected output. At runtime execute the whole functions chain Reader approach, the idea https://github.com/mw1980/ondependencyinjection/ the ‘functional’ package
24 Reader approach, the code https://github.com/mw1980/ondependencyinjection/ the ‘functional/reader’ package Class FunctionsRepository { static final Function<String, DecisionData> DECISION_DATA_SOURCE = id -> new DecisionData( COMPANY_EXPECTATION_SOURCE.apply(id), BRANCH_EXPECTATION_SOURCE.apply(id), TRANSACTION_INDICATOR_SOURCE.apply(id), MARKT_VOLATILITY_SOURCE.get()); static final Function<DecisionData, Action> TRANSACTION_RECOMMENDATION_LOGIC = decisionData -> { if (thingsLookGood(decisionData)) { return Action.BUY;} else if (thingsLookBad(decisionData)) { return Action.SELL;} else { return Action.WAIT;} };
25 Reader approach, the code https://github.com/mw1980/ondependencyinjection/ the ‘functional/reader’ package @Override public Action recommendedAction(String id) { return new Reader<>(DECISION_DATA_SOURCE) .map(TRANSACTION_RECOMMENDATION_LOGIC) .apply(id); }
26 Advantages: - This approach delivers a high level, functional and reusable solution - There is no dependency to any external framework - There is no container magic. The code is easy to read and test - It is suitable to inject low level functionality, like getting a database connection Drawbacks: - Complexity: The reader is a monad. Most developers avoid creating and using monads, since the monads have a reputation of being difficult to understand - Extra functionality, like scoping, needs to be implemented separately Reader approach, considerations https://github.com/mw1980/ondependencyinjection/ the ‘functional/reader’ package
27 Thanks!Any questions? You can find me at: ▫ marian.wamsiedel@gmail.com ▫ Linkedin: https://www.linkedin.com/in/marian-wamsiedel-4a1b792b/
28 Credits ▫ https://en.wikipedia.org/wiki/Dependency_injection ▫ https://en.wikipedia.org/wiki/Currying ▫ https://martinfowler.com/articles/injection.html ▫ https://www.yegor256.com/2014/10/03/di-containers-are-evil.html ▫ https://www.slideshare.net/mariofusco/from-object-oriented-to-functional-d omain-modeling
29 Credits Special thanks to all the people who made and released these resources for free: ▫ Presentation template by SlidesCarnival ▫ Photographs by Unsplash ▫ Photographs by pexels.com

Dependency injection in Java, from naive to functional

  • 1.
    Inversion of controlin Java from naive to functional
  • 2.
    2 Hello! My name isMarian Wamsiedel I am a Java developer You can find me at marian.wamsiedel@gmail.com
  • 3.
    The inversion ofcontrol (IoC), or dependency injection is an older concept in the software development The standard way to implement IoC is by using a container and a dependency injection framework, like Spring The rise of microservices and the functional programming techniques offers new solutions and challenge the IoC implementation standards Motivation Since there is plenty information available on the web about IoC, the presentation does not cover the basics of IoC, it just focuses on IoC implementation The presentation contains a naive IoC implementation, a standard one and two functional IoC approaches As always, choose the right tool for the job! The code examples are available on github. They are sample implementations and should not be used in this form in a real life scenario 3
  • 4.
    4 Inversion of control,the idea An object should receive its dependencies rather than create them This technique is largely used in Java enterprise applications, where services from the lower, or external layers are injected in objects from the upper, or internal layers Dependency injection allows clear separation between layers. The objects in the upper layers only need to know the contract that the services implement and not the concrete service implementation Dependency injection offers good support to unit testing. The tests can inject mocks, or test doubles, instead of real service implementations
  • 5.
    5 Inversion of control,example The concepts can be illustrated by a simple application, that calculates a buy / sell / hold recommendation for a stock on the market The recommendation is emitted by a transaction logic class and takes into consideration more factors, like the company and company’s branch perspective, if the stock is currently overbought, or oversold and the market volatility All these factors are delivered by helper classes, that are normally injected into the high-level transaction logic class The code examples are available on github: https://github.com/mw1980/ondependencyinjection/
  • 6.
    Inversion of control,approaches 6 ReaderCurryingIoC ContainerNaive
  • 7.
    7 The naive approach walkalone and nobody will distract you
  • 8.
    8 Naive approach, theidea Design your application with an “initialization” area. Initialize there all your services and all your high-level classes. Inject the service objects into the constructor of the high-level classes This area can be a class, like a service locator, if the application is small, or a collection of classes, if the application is bigger This way a pyramid of initialized objects ready to be used is created. When the application starts, pass the input parameters to the root object of this pyramid. The pyramid will be “alive” and will deliver the expected functionality https://github.com/mw1980/ondependencyinjection/ the ‘naive’ package
  • 9.
    9 Naive approach, thecode class ServiceLocator { static final TransactionLogic TRANSACTION_LOGIC = new NaiveTransactionLogic( new SampleStockAnalyst(), new SampleMarketAnalyst() ); public TransactionLogic transactionLogic() { return TRANSACTION_LOGIC; } } … //in the Main.java class public static void main(String[] args) { System.out.println( new ServiceLocator().transactionLogic().recommendedAction("BAY001")); } https://github.com/mw1980/ondependencyinjection/ the ‘naive’ package
  • 10.
    10 Naive approach, considerations Advantages: -The code is decoupled from any external dependencies - There is no container magic involved Drawbacks: - The “initialization” area tends to get really ugly, if the application grows in complexity and the number of objects to be initialized is too big - Some other usual requirements are not considered, like: objects scoping (singleton, request, session …) https://github.com/mw1980/ondependencyinjection/ the ‘naive’ package
  • 11.
    Inversion of control,approaches 11 ReaderCurryingIoC ContainerNaive
  • 12.
    12 The Ioc containerapproach Put all your eggs in the same basket
  • 13.
    13 Ioc container approach,the idea The standard approach to implement the inversion of control is to use a dependency injection framework and a container that stores all the objects that need to be injected The classes relevant for the dependency injection are configured in xml files, or coded in Java configuration files. The framework scans the configuration and populates the container with corresponding objects The dependency injection framework injects beans from the container into the new created objects, if the relevant classes contain some required annotations https://github.com/mw1980/ondependencyinjection/ the ‘guice’ package
  • 14.
    14 Ioc container approach,the code public class GuiceModule extends AbstractModule { @Override protected void configure() { bind(StockAnalyst.class).to(SampleStockAnalyst.class); bind(MarketAnalyst.class).to(SampleMarketAnalyst.class); bind(TransactionLogic.class).to(GuiceTransactionLogic.class); } } ... public static void main(String[] args) { Injector injector = Guice.createInjector(new GuiceModule()); TransactionLogic transactionLogic = injector.getInstance(TransactionLogic.class); System.out.println(transactionLogic.recommendedAction("ABEA")); } https://github.com/mw1980/ondependencyinjection/ the ‘guice’ package
  • 15.
    15 Ioc container approach,considerations Advantages: - This practice is already well known by the most developers, being the standard way to implement dependency injection - It is a comfortable solution to auto wire the project dependencies and quickly bootstrap any software project - The IoC frameworks also offer other functionality, like scoping Drawbacks: - The code is hard coupled on an external framework - Some form of container magic is involved: the overview is lost in a big project - It transforms compile time exceptions into the runtime exceptions, if the container configuration is invalid https://github.com/mw1980/ondependencyinjection/ the ‘guice’ package
  • 16.
    Inversion of control,approaches 16 ReaderCurryingIoC ContainerNaive
  • 17.
    17 The currying approach Usepartial fixing to completely fix the problem
  • 18.
    18 Currying approach, theidea See the situation through functional programming lens: Most of the application responsibilities can be seen as functions, that map some input to a result. The services that need to be injected are part of this function input Service instances can be injected as functions input parameters using partial fixing (currying). The function body contains only the business logic https://github.com/mw1980/ondependencyinjection/ the ‘functional’ package
  • 19.
    19 Currying approach, thecode class FunctionalModule { static final Function3<StockAnalyst, MarketAnalyst, String, Action> FULL_RECOMMENDATION = (stockAnalyst, marketAnalyst, stockId) -> Action.BUY; //real logic here to replace hard coded value static final Function<String, Action> SIMPLE_RECOMMENDATION = FULL_RECOMMENDATION .curried().apply(new SampleStockAnalyst()) .curried().apply(new SampleMarketAnalyst()); } ... public static void main(String[] args) { System.out.println(FunctionalModule.SIMPLE_RECOMMENDATION.apply("XYZ"));} https://github.com/mw1980/ondependencyinjection/ the ‘functional’ package
  • 20.
    20 Advantages: - It isa simple solution, it does not require any dependency injection framework - There is no container magic involved - It is suitable for small projects, like microservices Drawbacks: - Standard java does not offer easy currying functionality, therefore the project needs some external library, like java slang (vavr) - There is no extra functionality provided, like scoping Currying approach, considerations https://github.com/mw1980/ondependencyinjection/ the ‘functional’ package
  • 21.
    Inversion of control,approaches 21 ReaderCurryingIoC ContainerNaive
  • 22.
    22 The reader patternapproach Functional programming, bring on the big guns
  • 23.
    23 The reader isa container around a function: Reader<A, B> wraps around f : A -> B The reader allows the extraction of the value (of type B), by executing the wrapped function. It is also possible to compose the original function with a second one g: B -> C and receive a new reader container as result: MyReader[f : A -> B].map(g : B -> C) = NewReader[h: A -> C] Instead of injecting a service with one method, wrap the method logic in a function and create a reader around it. Use reader mapping functionality to implement the logic needed to calculate the expected output. At runtime execute the whole functions chain Reader approach, the idea https://github.com/mw1980/ondependencyinjection/ the ‘functional’ package
  • 24.
    24 Reader approach, thecode https://github.com/mw1980/ondependencyinjection/ the ‘functional/reader’ package Class FunctionsRepository { static final Function<String, DecisionData> DECISION_DATA_SOURCE = id -> new DecisionData( COMPANY_EXPECTATION_SOURCE.apply(id), BRANCH_EXPECTATION_SOURCE.apply(id), TRANSACTION_INDICATOR_SOURCE.apply(id), MARKT_VOLATILITY_SOURCE.get()); static final Function<DecisionData, Action> TRANSACTION_RECOMMENDATION_LOGIC = decisionData -> { if (thingsLookGood(decisionData)) { return Action.BUY;} else if (thingsLookBad(decisionData)) { return Action.SELL;} else { return Action.WAIT;} };
  • 25.
    25 Reader approach, thecode https://github.com/mw1980/ondependencyinjection/ the ‘functional/reader’ package @Override public Action recommendedAction(String id) { return new Reader<>(DECISION_DATA_SOURCE) .map(TRANSACTION_RECOMMENDATION_LOGIC) .apply(id); }
  • 26.
    26 Advantages: - This approachdelivers a high level, functional and reusable solution - There is no dependency to any external framework - There is no container magic. The code is easy to read and test - It is suitable to inject low level functionality, like getting a database connection Drawbacks: - Complexity: The reader is a monad. Most developers avoid creating and using monads, since the monads have a reputation of being difficult to understand - Extra functionality, like scoping, needs to be implemented separately Reader approach, considerations https://github.com/mw1980/ondependencyinjection/ the ‘functional/reader’ package
  • 27.
    27 Thanks!Any questions? You canfind me at: ▫ marian.wamsiedel@gmail.com ▫ Linkedin: https://www.linkedin.com/in/marian-wamsiedel-4a1b792b/
  • 28.
    28 Credits ▫ https://en.wikipedia.org/wiki/Dependency_injection ▫ https://en.wikipedia.org/wiki/Currying ▫https://martinfowler.com/articles/injection.html ▫ https://www.yegor256.com/2014/10/03/di-containers-are-evil.html ▫ https://www.slideshare.net/mariofusco/from-object-oriented-to-functional-d omain-modeling
  • 29.
    29 Credits Special thanks toall the people who made and released these resources for free: ▫ Presentation template by SlidesCarnival ▫ Photographs by Unsplash ▫ Photographs by pexels.com