13

I have TypeScript application and I'm using Inversify for IoC.

I have a connection class:

'use strict';
import { injectable } from 'inversify';
import { createConnection, Connection } from "typeorm";
import { Photo, PhotoMetadata, Author, Album } from '../index';

@injectable()
class DBConnectionManager {

    public createPGConnection(): Promise<Connection> {
        return createConnection({
            driver: {
                type: "postgres",
                host: "host",
                port: 5432,
                username: "username",
                password: "password",
                database: "username"
            },
            entities: [
                Photo, PhotoMetadata, Author, Album
            ],
            autoSchemaSync: true,
        });

    }

}

export { DBConnectionManager };

After I created my connection I want to bind a connection into my container:

kernel.bind<Connection>('DefaultConnection').toConstantValue(getConnectionManager().get());

and then I want to inject it into another class:

import { injectable, inject } from 'inversify';
import { Connection, FindOptions } from "typeorm";
import { IGenericRepository, ObjectType } from '../index';


    @injectable()
    class GenericRepository<T> implements IGenericRepository<T> {

        private connection: Connection;
        private type: ObjectType<T>;

        constructor( @inject('DefaultConnection') connection: Connection) {
            this.connection = connection;
        }

So in my container configuration how can I bind DefaultConnection that needs to wait for CreateConnection I can do with async and wait but I'm wonder if there is a cleaner way to achive this in inversify

user4092086
  • 986
  • 3
  • 12
  • 24

2 Answers2

23

Inversify 2.0 includes support for asynchronous factories (AKA Providers)

A provider allows you can to declare a provider as follows:

container.bind<<DbClient>("DbClient").to(DbClientClass);

container.bind<interfaces.Provider<DbClient>>("Provider<DbClient>")
         .toProvider<DbClient>((context) => {
            return () => {
                return new Promise<DbClient>((resolve, reject) => {

                    // Create instance
                    let dbClient = context.container.get<DbClient>("DbClient");

                    // Open DB connection
                    dbClient.initialize("//connection_string")
                            .then(() => {
                                resolve(dbClient);
                            })
                            .catch((e: Error) => {
                                reject(e);
                            });
                });
            };
        });

Then you can inject and consume the provider. The only problem is that it requires two-step initialization: the constructor injection and the async getDb() method.

class UserRepository { 

    private _db: DbClient;
    private _dbProvider: Provider<DbClient>;

    // STEP 1
    // Inject a provider of DbClient to the constructor
    public constructor(
        @inject("Provider<DbClient>") provider: Provider<DbClient>
    ) { 
        this._dbProvider = provider;
    }

    // STEP 2
    // Get a DB instance using a provider
    // Returns a cached DB instance if it has already been created
    private async getDb() {
        if (this._db) return this._db;
        this._db = await this._dbProvider();
        return Promise.resolve(this._db);
    }

    public async getUser(): Promise<Users[]>{
        let db = await this.getDb();
        return db.collections.user.get({});
    }

    public async deletetUser(id: number): Promise<boolean>{
        let db = await this.getDb();
        return db.collections.user.delete({ id: id });
    }

}

We are working on a new feature to simplify the injection of asynchronous values. This feature will be included in inversify 3.0:

class UserRepository { 

    // STEP 1
    public constructor(
        @inject("Provider<DbClient>") private provider: Provider<DbClient>
    ) {}

    public async getUser(): Promise<Users[]>{
        // STEP 2: (No initialization method is required)
        let db = await this.provider.someFancyNameForProvideValue;
        return db.collections.user.get({});
    }
}
Ivan Mushketyk
  • 8,107
  • 7
  • 50
  • 67
Remo H. Jansen
  • 23,172
  • 11
  • 70
  • 93
  • Is the one phase initialization already implemented? I cannot find any documentation on how to use it – niqui Aug 08 '22 at 14:16
0

Just create connection at application startup and bind already connected instance, usually you don't really need to defer connection.

Mikhail Elfimov
  • 153
  • 1
  • 6
  • This would work if your application runs continuously, and the assumption is that all initialization/dependency building is done before the first request comes in. In the case if you are trying to deploy to Serverless Framework, your application doesn't get initialized until the request comes in. This means you will run into timing/race condition. In my case, the IoC container isn't finished setting up and therefore the application ran into runtime error because the dependencies are building while processing the request. – DLee Feb 03 '21 at 06:35