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