$30 off During Our Annual Pro Sale. View Details »

Hot Topic: Dependency Injection - Let's Talk about it

Hot Topic: Dependency Injection - Let's Talk about it

Dependency Injection (DI). By now, everyone should have heard about it.
But what actually is "Dependency Injection" and "Inversion of Control"?
We can find 'it' implemented in a lot of frameworks nowadays and for
someone not familiar with the concept it might seem like magic.
In this session we are going to take a closer look behind the curtains
of this software design principle and show what DI can do for us and how
a very simplistic version could be implemented in Javascript.

Yannick Baron

February 13, 2020
Tweet

More Decks by Yannick Baron

Other Decks in Programming

Transcript

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

    View Slide

  2. "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)

    View Slide

  3. • 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)

    View Slide

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

    View Slide

  5. // Consumers

    new Consumer1();

    new Consumer2();
    what do consumers depend on?

    View Slide

  6. // 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)

    View Slide

  7. // 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

    View Slide

  8. // 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

    View Slide

  9. • 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

    View Slide

  10. • 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

    View Slide

  11. Let's build it!
    STOP. DEMOTIME.

    View Slide

  12. // 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

    View Slide

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

    View Slide

  14. Quick Interlude: Decorators

    View Slide

  15. 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');

    }

    ...

    }

    View Slide

  16. 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');

    }

    ...

    }

    View Slide

  17. • 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

    View Slide

  18. 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[] {

    ...

    }


    View Slide

  19. • upcoming spec in JavaScript

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

    View Slide

  20. • 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

    View Slide

  21. • 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

    View Slide

  22. Let's have a look at some code

    View Slide

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

    View Slide