The presentation contains more approaches to implement inversion of control (dependency injection). There is a naive implementation, a standard guice implementation and two functional solutions.
The code samples are available on a github repository.
2. 2
Hello!
My name is Marian Wamsiedel
I am a Java developer
You can find me at marian.wamsiedel@gmail.com
3. 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. 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/
8. 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. 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. 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
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
18. 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. 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. 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
23. 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. 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. 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. 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. 27
Thanks!Any questions?
You can find me at:
▫ marian.wamsiedel@gmail.com
▫ Linkedin: https://www.linkedin.com/in/marian-wamsiedel-4a1b792b/
29. 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