All Projects → radzserg → rsdi

radzserg / rsdi

Licence: Apache-2.0 license
Dependency Injection Container

Programming Languages

typescript
32286 projects

Projects that are alternatives of or similar to rsdi

AutoDI
Dependency injection made simple.
Stars: ✭ 87 (+93.33%)
Mutual labels:  dependency-injection
tsdi
Dependency Injection container (IoC) for TypeScript
Stars: ✭ 50 (+11.11%)
Mutual labels:  dependency-injection
DependencyInjector
Lightweight dependency injector
Stars: ✭ 30 (-33.33%)
Mutual labels:  dependency-injection
noicejs
extremely thin async dependency injection
Stars: ✭ 16 (-64.44%)
Mutual labels:  dependency-injection
android-base-project
Android LateralView Base Project
Stars: ✭ 25 (-44.44%)
Mutual labels:  dependency-injection
di-comparison
DI containers comparison
Stars: ✭ 20 (-55.56%)
Mutual labels:  dependency-injection
toolkit
some useful library of the php
Stars: ✭ 15 (-66.67%)
Mutual labels:  dependency-injection
KataSuperHeroesIOS
Super heroes kata for iOS Developers. The main goal is to practice UI Testing.
Stars: ✭ 69 (+53.33%)
Mutual labels:  dependency-injection
mvp-architecture-kotlin-dagger-2-retrofit-android
Android Application MVP (Model-View-Presenter) architecture example using Dagger2 Dependency Injection (DI) and Retrofit Tutorial using Kotlin programming language.
Stars: ✭ 15 (-66.67%)
Mutual labels:  dependency-injection
Mimick.Fody
An integrated framework for dependency injection and aspect-oriented processing.
Stars: ✭ 15 (-66.67%)
Mutual labels:  dependency-injection
inject
[Archived] See https://github.com/goava/di.
Stars: ✭ 49 (+8.89%)
Mutual labels:  dependency-injection
lightsaber
Compile time dependency injection framework for JVM languages. Especially for Kotlin.
Stars: ✭ 119 (+164.44%)
Mutual labels:  dependency-injection
component-manager
component framework and dependency injection for golang
Stars: ✭ 21 (-53.33%)
Mutual labels:  dependency-injection
vue3-oop
使用类和依赖注入写vue组件
Stars: ✭ 90 (+100%)
Mutual labels:  dependency-injection
Spork
Annotation processing and dependency injection for Java/Android
Stars: ✭ 77 (+71.11%)
Mutual labels:  dependency-injection
magnet
Dependency injection library for modular Android applications
Stars: ✭ 174 (+286.67%)
Mutual labels:  dependency-injection
unbox
Fast, simple, easy-to-use DI container
Stars: ✭ 45 (+0%)
Mutual labels:  dependency-injection
test-tools
Improves PHPUnit testing productivity by adding a service container and self-initializing fakes
Stars: ✭ 25 (-44.44%)
Mutual labels:  dependency-injection
solid-services
Solid.js library adding a services layer for global shared state.
Stars: ✭ 34 (-24.44%)
Mutual labels:  dependency-injection
solid
Solid Android components
Stars: ✭ 33 (-26.67%)
Mutual labels:  dependency-injection

RSDI - Dependency Injection Container

Simple and powerful dependency injection container for JavaScript/TypeScript.

Getting Started

Given that you have classes and factories in your application

class CookieStorage {}
class AuthStorage {
    constructor(storage: CookieStorage) {}
}
class Logger {}
class DummyLogger extends Logger {}

function loggerFactory(container: IDIContainer): Logger {
    const env = container.get("ENV");
    if (env === "test") {
        return new DummyLogger();
    }
    return new Logger();
}
function UsersRepoFactory(knex: Knex): UsersRepo {
    return {
        async findById(id: number) {
            await knex("users").where({ id });
        },
    };
}

Your DI container initialisation will include

import DIContainer, { object, use, factory, func, IDIContainer } from "rsdi";

export default function configureDI() {
    const container = new DIContainer();
    container.add({
        ENV: "test", // define raw value
        Storage: object(CookieStorage), // constructor without arguments
        AuthStorage: object(AuthStorage).construct(
            use(Storage) // refer to another dependency
        ),
        knex: knex(), // keeps raw value
        Logger: factory(loggerFactory), // lazy function, will be resolved when it will be needed
        UsersRepo: func(UsersRepoFactory, use("knex")),
    });
    return container;
}

The entry point of your application will include

const container = configureDI();
const env: string = container.get("ENV"); // test
const authStorage: AuthStorage = container.get(AuthStorage); // object of AuthStorage
const logger: Logger = container.get(loggerFactory); // object of DummyLogger

All resolvers are resolved only once and their result persists over the life of the container.

Features

  • Simple but powerful
  • Does not requires decorators
  • Great types resolution
  • Works great with both javascript and typescript

Motivation

Popular dependency injection libraries use reflect-metadata that allows to fetch argument types and based on those types and do autowiring. Autowiring is a nice feature but the trade-off is decorators.

@injectable()
class Foo {}

Why component Foo should know that it's injectable?

Your business logic depends on a specific framework that is not part of your domain model and can change.

More thoughts in a dedicated article

Usage

Raw values

Dependencies are set as raw values. No lazy initialisation happens. Container keeps and return raw values.

import DIContainer from "rsdi";

const container = new DIContainer();
container.add({
    ENV: "PRODUCTION",
    HTTP_PORT: 3000,
    storage: new CookieStorage(),
});
const env: string = container.get("ENV");
const port: number = container.get("HTTP_PORT");
const authStorage: AuthStorage = container.get(AuthStorage); // instance of AuthStorage

Object resolver

object(ClassName) - constructs an instance of the given class. The simplest scenario it calls the class constructor new ClassName(). When you need to pass arguments to the constructor, you can use construct method. You can refer to the already defined dependencies via the use helper, or you can pass raw values.

If you need to call object method after initialization you can use method it will be called after constructor.

// test class
class ControllerContainer {
    constructor(authStorage: AuthStorage, logger: Logger) {}

    add(controller: Controller) {
        this.controllers.push(controller);
    }
}

// container
const container = new DIContainer();
container.add({
    Storage: object(CookieStorage), // constructor without arguments
    AuthStorage: object(AuthStorage).construct(
        use(Storage) // refers to existing dependency
    ),
    UsersController: object(UserController),
    PostsController: object(PostsController),
    ControllerContainer: object(MainCliCommand)
        .construct(use(AuthStorage), new Logger()) // use existing dependency, or pass raw values
        .method("add", use(UsersController)) // call class method after initialization
        .method("add", use(PostsController)),
});

Function resolver

Function resolver allows declaring lazy functions. Function will be called when it's actually needed.

function UsersRepoFactory(knex: Knex): UsersRepo {
    return {
        async findById(id: number) {
            await knex("users").where({ id });
        },
    };
}

const container = new DIContainer();
container.add({
    DBConnection: knex(/* ... */),
    UsersRepoFactory: func(UsersRepoFactory, use("DBConnection")),
});

const userRepo = container.get(UsersRepoFactory);

Factory resolver

Factory resolver is similar to a Function resolver. You can use factory resolver when you need more flexibility during initialization. container: IDIContainer will be passed in as an argument to the factory method. So you can resolve other dependencies inside the factory function.

const container = new DIContainer();
container.add({
    BrowserHistory: factory(configureHistory),
});

function configureHistory(container: IDIContainer): History {
    const history = createBrowserHistory();
    const env = container.get("ENV");
    if (env === "production") {
        // do what you need
    }
    return history;
}
const history = container.get<History>("BrowserHistory");

Advanced Usage

Typescript type resolution

container.get and use helper resolve type based on following convention:

  • if given name is class - instance of a class
  • if given name is function - return type of function
  • if custom generic type is provided - custom type
  • otherwise - any
const container: DIContainer = new DIContainer();
container.add({
    Bar: new Bar(),
    Foo: new Bar(), // fake foo example
});
let bar: Bar = container.get(Bar); // types defined based on a given type Bar
let foo: Foo = container.get(Foo); // you can trick TS compiler (it's your responsibility)
let foo2: Foo = container.get<Foo>("Foo"); // custom generic type is provided
let foo3: Foo = container.get("Foo"); // any type

Example: use defines type for a class constructor.

class Foo {
    constructor(private readonly bar: Bar) {}
}

const container: DIContainer = new DIContainer();
container.add({
    Bar: new Bar(),
    Foo: object(Foo).construct(use(Bar)), // constuct method checks type and use return Bar type
});

Example: container.get resolve type based on a given factory return type.

function myFactory() {
    return { a: 123 };
}
container.add({
    myFactory: factory((container: IDIContainer) => {
        return myFactory();
    }),
});
let { a } = container.get(myFactory);

Dependency declaration

RSDI resolves dependencies on a given type. It can be string or function. In the simplest case, you can use strings.

container.add({
    Foo: new Foo(),
});
const foo = container.get<Foo>("Foo");

In order to avoid magic strings you can operate with types.

const foo = container.get(Foo);

RSDI uses Foo.name behind the scene that equals to "Foo". Remember that this approach will not work for uglified code. You can also rename the function Foo => Buzz, and forget to rename the declaration. From that perspective you can declare dependencies this way.

container.add({
    [Foo.name]: new Foo(),
    [MyFactory.name]: MyFactory(),
});
const foo = container.get(Foo);
const buzz = container.get(MyFactory);

Async factory resolver

RSDI intentionally does not provide the ability to resolve asynchronous dependencies. The container works with resources. All resources will be used sooner or later. The lazy initialization feature won't be of much benefit in this case. At the same time, mixing synchronous and asynchronous resolution will cause confusion primarily for the consumers.

The following approach will work in most scenarios.

// UserRepository.ts
class UserRepository {
    public constructor(private readonly dbConnection: any) {} // some ORM that requires opened connection

    async findUser() {
        return await this.dbConnection.find(/*...params...*/);
    }
}

// configureDI.ts
import { createConnections } from "my-orm-library";
import DIContainer, { factory, use } from "rsdi";

async function configureDI() {
    // initialize async factories before DI container initialisation
    const dbConnection = await createConnections();

    const container = new DIContainer();
    container.add({
        DbConnection: dbConnection,
        UserRepository: object(UserRepository).construct(use("DbConnection")),
    });
    return container;
}

// main.ts
const diContainer = await configureDI();
const userRepository = diContainer.get<UserRepository>("UserRepository");
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].