Slide 1

Slide 1 text

Hot Topic: Dependency Injection Let's Talk about it! Yannick Baron https://www.thinktecture.com/yannick-baron

Slide 2

Slide 2 text

"list dependencies in the constructor and magically have them available" • design pattern based on the Inversion of Control principle • different styles of injection (Constructor, Setter, ...) Dependency Injection (DI)

Slide 3

Slide 3 text

• principle in software design • take control from individual class (or function) • give control to application • essentially: making things pluggable → Dependency Injection is a way to achieve Inversion of Control Inversion of Control (IoC)

Slide 4

Slide 4 text

• transparency: easily determine dependencies of a class • reuse: pluggable logic / change behaviour "from the outside" • testing: a whole lot easier Why Dependency Injection?

Slide 5

Slide 5 text

// Consumers
 new Consumer1();
 new Consumer2(); what do consumers depend on?

Slide 6

Slide 6 text

// Consumers
 Consumer1() {
 new FileLogger();
 new Communicator();
 }
 Consumer2() {
 new FileLogger();
 new Communicator();
 new AuthHandler();
 } what do consumers depend on? consumers should not have to know about how to instantiate Service classes no way to replace an implementation (FileLogger vs ConsoleLogger)

Slide 7

Slide 7 text

// Services
 FileLogger() { new FileWriter(); }
 HttpClient() { }
 AuthHandler() { new HttpClient(); }
 Communicator() { new HttpClient(); } // Consumers
 Consumer1() {
 new FileLogger();
 new Communicator();
 }
 Consumer1() {
 new FileLogger();
 new Communicator();
 new AuthHandler();
 } what do consumers depend on? consumers should not have to know about how to instantiate Service classes no way to replace an implementation (FileLogger vs ConsoleLogger) what if we want the same instance of the HttpClient in multiple classes? (singleton? yikes.) duplication of construction logic

Slide 8

Slide 8 text

// Services
 const logs = new FileLogger();
 const http = new HttpClient();
 const comm = new Communicator(http);
 const auth = new AuthHandler(http);
 
 // Consumers
 new Consumer1(logs, comm);
 new Consumer2(logs, comm, auth); easy to identify dependencies consumer does not know about the intricacies of instantiating a dependency easily replace a dependency (interfaces!) same instance goes to different consumers creation logic once where we bootstrap the app

Slide 9

Slide 9 text

• collection of creation logic and instances • keeps all creation logic in a single place • keeps creation logic out of our classes • can create an instance when it is needed Dependency Injection Containers

Slide 10

Slide 10 text

• small project with very few classes → no DI Container needed • huge projects with multiple entry points do not need instances of all classes
 e.g. API request to single controller (simple dispatcher)
 route definition → instantiates controller → needs dependencies → responds • after setup, simple usage:
 
 const consumer1 = container.get(Consumer1);
 • dependencies created for us but still replaceable Dependency Injection Containers

Slide 11

Slide 11 text

Let's build it! STOP. DEMOTIME.

Slide 12

Slide 12 text

// Service Locator
 new Consumer1(container)
 new Consumer2(container) // Dependency Injection
 new Consumer1(
 container->get(FileLogger),
 container->get(Communicator),
 )
 new Consumer2(
 container->get(FileLogger),
 container->get(Communicator),
 container->get(AuthHandler),
 )
 Service Locator hides dependencies complicates creation logic couples consumers to container couples the whole app

Slide 13

Slide 13 text

Automagic DI ... or how it's done in Typescript

Slide 14

Slide 14 text

Quick Interlude: Decorators

Slide 15

Slide 15 text

function getUserRoles(user): Role[] {
 if (!userExists(user) || !isLoggedIn(user)) {
 throw Error('The user does not exist or is not logged in');
 }
 ...
 } function getUserPermissions(user): Permission[] {
 if (!userExists(user) || !isLoggedIn(user)) {
 throw Error('The user does not exist or is not logged in');
 }
 ...
 }

Slide 16

Slide 16 text

function getUserRoles(user: User): Role[] {
 if (!userExists(user) || !isLoggedIn(user)) {
 throw Error('The user does not exist or is not logged in');
 }
 ...
 } function getUserPermissions(user: User): Permission[] {
 if (!userExists(user) || !isLoggedIn(user)) {
 throw Error('The user does not exist or is not logged in');
 }
 ...
 }

Slide 17

Slide 17 text

• design pattern • function or class that wraps another function or class • can be used to reduce code duplication • can be used to extend functionality of the wrapped object / function Decorators

Slide 18

Slide 18 text

function LoggedInUser(target: any) {
 const user = getUserParam(target);
 if (!userExists(user) || !isLoggedIn(user)) {
 throw Error('The user does not exist or is not logged in');
 }
 return target;
 }
 
 @LoggedInUser // Syntactic Sugar
 function getUserRoles(user: User): Role[] {
 ...
 }
 
 @LoggedInUser
 function getUserPermissions(user: User): Permission[] {
 ...
 }


Slide 19

Slide 19 text

• upcoming spec in JavaScript
 (https://github.com/tc39/proposal-decorators) • member decorators • class decorators • built into TypeScript already enableDecoratorSupport Decorators in JavaScript

Slide 20

Slide 20 text

• construction logic is essentially listing the dependencies • goal: get list of constructor parameters • potential solution: turn class into a string and parse for constructor
 caveat: mangling code changes parameter names
 (remember AngularJS and the dependency list of strings?) • problem: JS has no types, type information will be lost Automagic DI with Decorators

Slide 21

Slide 21 text

• there is a way in TypeScript to write type information of decorated classes: emitDecoratorMetadata • how to access said metadata? • JavaScript: part of the decorator proposal • preliminary implementation https://www.npmjs.com/package/reflect-metadata Automagic DI with Decorators in TypeScript

Slide 22

Slide 22 text

Let's have a look at some code

Slide 23

Slide 23 text

It's a wrap! https://thinktecture.com/yannick-baron