blob: 2f57c82df4678ce872fcaa4e9cfcf84846252cd4 [file] [log] [blame]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=edge"><![endif]-->
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="generator" content="Asciidoctor 1.5.7.1">
<title>Dependency Injection in N4JS</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<!-- ************* Meta ************* -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1" />
<!-- ************* OpenGraph ************-->
<meta name="description" content="The Eclipse N4JS language and its IDE enable high-quality JavaScript development for large Node.js projects.">
<meta property="og:site_name" content="Eclipse N4JS"/>
<meta property="og:title" content="Eclipse N4JS Language and IDE"/>
<meta property="og:url" content="https://numberfour.github.io/n4js"/>
<meta property="og:description" content="The Eclipse N4JS language and its IDE enable high-quality JavaScript development for large Node.js projects."/>
<meta property="og:image" content="../images/n4js.png">
<!-- ************* Favicon ************-->
<link rel="icon" href="../images/favicon.ico" />
<link rel="icon" type="image/png" href="../images/favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="../images/favicon-16x16.png" sizes="16x16" />
<!-- ************* Back-to-top JQuery ************* -->
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.0/jquery-ui.js"></script>
<!-- ************* Prism.js Syntax Highlighter ******-->
<link href="../styles/prism.min.css" rel="stylesheet"/>
<script src="../scripts/prism.js"></script>
<!-- ************* Styles ************* -->
<link rel="stylesheet" type="text/css" href="../styles/n4js-adoc.css">
<!-- ****************** NavBar ****************** -->
<div id="menubar">
<div class="banner">
<a href="../index.html"><img id="logo" src="../images/n4js-logo.png" alt="Eclipse N4JS Language and IDE"></a>
</div>
<ul>
<li><a href="../downloads.html"></i>Download</a></li>
<li><a href="../community.html"></i>Community</a></li>
<li><a href="../userguides/index.html">Documentation</a></li>
</ul>
</div>
<button id="tocbutton">TOC</button>
</head>
<body class="book">
<div id="header">
</div>
<div id="content">
<h1 id="_dependency_injection_in_n4js" class="sect0"><a class="link" href="#_dependency_injection_in_n4js">Dependency Injection in N4JS</a></h1>
<div class="openblock partintro">
<div class="content">
<div class="paragraph">
<div class="title">Dependency Injection in N4JS</div>
<p>Dependency injection (DI) is a concept that allows for configuring dependencies
between classes at a central location. Instead of passing dependencies from class to class, N4JS' built-in
DI support does this automatically.</p>
</div>
<div class="paragraph">
<p>This makes the user code much cleaner, easier to maintain and also improves
its testability. N4JS DI framework follows Java JSR-330/Google Guice, making its usage easy for Java developers.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_application_object_graph"><a class="link" href="#_application_object_graph">Application Object Graph</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p>In object oriented languages, applications are composed from objects that interact with each other. Instances
of those objects need to be created and wired together on application startup to create a so-called object
graph of the application. While it&#8217;s possible to manually create this object graph, it quickly becomes
complicated. This is especially so if we want flexibility and reconfigurability to be long-lasting features
of our application.</p>
</div>
<div class="paragraph">
<p>Solutions for manually wiring the object graph come with their distinct disadvantages:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Hard coding dependencies makes code inflexible and complicate testing.</p>
</li>
<li>
<p>Passing dependencies to constructors bloats the constructors.</p>
</li>
<li>
<p>Using factories requires passing the factory and also bloats the code.</p>
</li>
</ul>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_dependency_injection_di"><a class="link" href="#_dependency_injection_di">Dependency Injection (DI)</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p>Dependency injection (DI) and DI frameworks aim to help with issues described before, specifically with the
following:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>The object graph is created automatically which removes burden of writing object factories.</p>
</li>
<li>
<p>Injection of the created instances is done behind the scenes where needed, which separates object-creation
from object-usage and keeps constructors simple.</p>
</li>
<li>
<p>The application&#8217;s configuration can be changed without changing its components.</p>
</li>
</ul>
</div>
<div class="paragraph">
<p>N4JS provides built-in support for dependency injection using a lightweight syntax with annotation similar to
Java <a href="https://jcp.org/en/jsr/detail?id=330">JSR-330</a> / <a href="https://github.com/google/guice">Google Guice</a>.
The N4JS testing framework also supports dependency injection which allows for special test settings in order to test components individually.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_example"><a class="link" href="#_example">Example</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p>In the following example, two versions of a simple weather application are implemented. Both versions use a
module WeatherEngine which returns the temperature for a given city. For this example, we use a timeout to
emulate a real request to a weather server:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-n4js" data-lang="n4js">export public class WeatherEngine {
data = [ {city: 'Berlin', temp: 5}, {city: 'Hamburg', temp: 15}, {city: 'Palo Alto', temp: 10} ];
public temperature(city: string): Promise&amp;lt;number, ?&amp;gt; {
return new Promise&amp;lt;number, any&amp;gt;(
(cb: {function(number)}) =&amp;gt; {
setTimeout(() =&amp;gt; cb(this.data.find(e =&amp;gt; e.city == city).temp) , Math.random() * 2000);
});
}
}</code></pre>
</div>
</div>
<div class="paragraph">
<p>In order to keep the examples as small as possible, in the non-DI version no manual wiring of the dependencies
is used. The components are instead set up by initializing the fields directly.</p>
</div>
<table class="tableblock frame-all grid-all stretch">
<colgroup>
<col style="width: 50%;">
<col style="width: 50%;">
</colgroup>
<tbody>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>Without Dependency-Injection</strong></p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock"><strong>With Dependency-Injection</strong></p></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><div class="content"><div class="listingblock">
<div class="title">WeatherApp.n4js</div>
<div class="content">
<pre class="highlight"><code class="language-n4js" data-lang="n4js">import { WeatherEngine } from 'WeatherEngine';
export class WeatherApp {
private engine: WeatherEngine = new WeatherEngine();
async printTemp(city: string): string {
return city + ': ' + (await this.engine.temperature(city));
}
}
export class Server {
weatherApp: WeatherApp = new WeatherApp();
async run() {
for (var s of ['Berlin', 'Hamburg', 'Palo Alto']) {
console.log(await this.weatherApp.printTemp(s));
}
}
}</code></pre>
</div>
</div></div></td>
<td class="tableblock halign-left valign-top"><div class="content"><div class="listingblock">
<div class="title">WeatherAppDI.n4js</div>
<div class="content">
<pre class="highlight"><code class="language-n4js" data-lang="n4js">import { WeatherEngine } from 'WeatherEngine';
export class WeatherApp {
@Inject private engine: WeatherEngine;
async printTemp(city: string): string {
return city + ': ' + (await this.engine.temperature(city));
}
}
@GenerateInjector
export class Server {
@Inject weatherApp: WeatherApp;
async run() {
for (var s of ['Berlin', 'Hamburg', 'Palo Alto']) {
console.log(await this.weatherApp.printTemp(s));
}
}
}</code></pre>
</div>
</div></div></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><div class="content"><div class="listingblock">
<div class="title">Starter.n4js</div>
<div class="content">
<pre class="highlight"><code class="language-n4js" data-lang="n4js">import { Server } from 'WeatherApp'
var server = new Server();
server.run();</code></pre>
</div>
</div></div></td>
<td class="tableblock halign-left valign-top"><div class="content"><div class="listingblock">
<div class="title">StarterDI.n4js</div>
<div class="content">
<pre class="highlight"><code class="language-n4js" data-lang="n4js">import { Server } from 'WeatherAppDI';
import { N4Injector } from 'n4js/lang/N4Injector';
var server = N4Injector.of(Server).create(Server);
server.run();</code></pre>
</div>
</div></div></td>
</tr>
</tbody>
</table>
<div class="paragraph">
<p>The changes are only minimal: Instead of creating the field instances directly, they are annotated
with <code>@Inject</code>. This should be familiar to Java programmers having used Google Guice.</p>
</div>
<div class="paragraph">
<p>An interesting aspect of dependency injection is how to set up the injector. In N4JS, the
annotation <code>@GenerateInjector</code> is used in order mark a class as a dependency
injection component. In other words, to associate an injector with the class. Running the server now
requires slightly different instantiation. Instead of constructing the server with <code>new</code>,
the built-in class <code>N4Injector</code> is used to create the first instance.</p>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_application_reconfigurability"><a class="link" href="#_application_reconfigurability">Application Reconfigurability</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p>A very useful quality of DI is its flexibility. This is particularly beneficial during testing. Let&#8217;s
write a test class for our <code>WeatherApp</code>. We do not want to wait an arbitrary amount of
seconds to receive the results of our test, we want to use a special version of the <code>WeatherEngine</code>
which immediately returns a value. Let&#8217;s have a look at the test module:</p>
</div>
<div class="listingblock">
<div class="content">
<pre class="highlight"><code class="language-n4js" data-lang="n4js">import { WeatherApp } from 'WeatherAppDI';
import { WeatherEngine } from 'WeatherEngine';
import { Assert } from 'n4/mangel/assert/Assert';
class WeatherEngineMock extends WeatherEngine { <i class="conum" data-value="1"></i><b>(1)</b>
@Override
public async temperature(city: string): number {
return 1;
}
}
@Binder <i class="conum" data-value="2"></i><b>(2)</b>
@Bind(WeatherEngine, WeatherEngineMock)
class WeatherAppTestConfig{ }
@GenerateInjector() <i class="conum" data-value="3"></i><b>(3)</b>
@UseBinder(WeatherAppTestConfig) <i class="conum" data-value="4"></i><b>(4)</b>
export class WeatherAppTest {
@Inject weatherApp: WeatherApp;
@Test public async test() {
Assert.equal(await this.weatherApp.printTemp('Berlin'), 'Berlin: 1');
}
}</code></pre>
</div>
</div>
<div class="colist arabic">
<table>
<tr>
<td><i class="conum" data-value="1"></i><b>1</b></td>
<td>A mock engine must be written.</td>
</tr>
<tr>
<td><i class="conum" data-value="2"></i><b>2</b></td>
<td>Followed by a "binder" which is a configuration telling the injector what type has to be used to instantiate objects. By default, the
injector uses the same class as referenced in the code. We change this and bind the mock engine to the
real engine.</td>
</tr>
<tr>
<td><i class="conum" data-value="3"></i><b>3</b></td>
<td>As the N4JS test framework already supports DI, we can declare the test as a new dependency injection.
component.</td>
</tr>
<tr>
<td><i class="conum" data-value="4"></i><b>4</b></td>
<td>Specific test configuration. The important point is that the
class <code>WeatherApp</code> now gets the <code>WeatherEngineMock</code> injected.</td>
</tr>
</table>
</div>
</div>
</div>
<div class="sect1">
<h2 id="_advanced_features"><a class="link" href="#_advanced_features">Advanced features</a></h2>
<div class="sectionbody">
<div class="paragraph">
<p>Specific advantages and extended DI features are discussed in greater detail in the N4JS language
spec. Some of the most notable features are:</p>
</div>
<div class="ulist">
<ul>
<li>
<p>Built-in pseudo-scope via <code>@Singleton</code>.</p>
</li>
<li>
<p>Possibility of nesting injectors via <code>@WithParentInjector</code>.</p>
</li>
<li>
<p>Built-in <code>Provider</code> type and possibility to create custom providers via <code>@Provides</code> to dynamically create instances.</p>
</li>
<li>
<p>Automatic resolution of dependency cycles.</p>
</li>
</ul>
</div>
</div>
</div>
</div>
<div id="footer">
<div id="footer-text">
</div>
</div>
<div class="Grid social" style="color:#d5dfea">
<div class="Cell Cell--2-12 m-Cell--withMargin">
<h2>Quick Links</h2>
<ul>
<li><a href="../downloads.html">Download</a></li>
<li><a href="../userguides/index.html">Documentation</a></li>
<li><a href="https://github.com/eclipse/n4js/">Source</a></li>
<li><a href="https://github.com/eclipse/n4js/issues">Issues</a></li>
</ul>
</div>
<div class="Cell Cell--2-12 m-Cell--withMargin">
<br/><br/>
<ul>
<li><a href="https://www.eclipse.org/forums/index.php/f/365/">Forum</a></li>
<li><a href="http://n4js.blogspot.de/">Blog</a></li>
<li><a href="https://dev.eclipse.org/mailman/listinfo/n4js-dev">Mailing List</a></li>
<li><a href="https://projects.eclipse.org/projects/technology.n4js">Eclipse Project Page</a></li>
<li><a href="https://twitter.com/n4jsdev">Tweets by n4jsdev</a></li>
</ul>
</div>
<div class="Cell Cell--2-12 m-Cell--withMargin">
<br/><br/>
<ul>
<li><a href="http://www.eclipse.org/">Eclipse Home</a></li>
<li><a href="http://www.eclipse.org/legal/privacy.php">Privacy Policy</a></li>
<li><a href="http://www.eclipse.org/legal/termsofuse.php">Terms of Use</a></li>
<li><a href="http://www.eclipse.org/legal/copyright.php">Copyright Agent</a></li>
<li><a href="http://www.eclipse.org/legal/">Legal</a></li>
</ul>
</div>
<div style="clear: both; height: 0; overflow: hidden;"></div>
</div>
<script>
// Toggle the table of contents
$( "button#tocbutton" ).click(function() {
if ($("#tocbutton").css('right') == '25px') {
$( "#tocbutton" ).animate({right: '215px'},"slow");
$( "#toc.toc2" ).animate({right: '0'},"slow");
}
else {
$( "#tocbutton" ).animate({right: '25px'},"slow");
$( "#toc.toc2" ).animate({right: '-13rem'},"slow");
}
});
</script>
<script type="text/javascript">
// Create a back to top button
$('body').prepend('<a href="#" class="back-to-top">Back to Top</a>');
var amountScrolled = 300;
$(window).scroll(function() {
if ( $(window).scrollTop() > amountScrolled ) {
$('a.back-to-top').fadeIn('slow');
} else {
$('a.back-to-top').fadeOut('slow');
}
});
$('a.back-to-top, a.simple-back-to-top').click(function() {
$('html, body').animate({
scrollTop: 0
}, 700);
return false;
});
</script>
</body>
</html>