owja/ioc

wire/inject/resolve strictly binds service to specific container

Closed this issue · 7 comments

Hi, i use @owja/ioc in Node.js app.

In my app i have a service (for example named MyService) that has one dependency.

I use wire function for define dependency.

In my case i have two containers in two apps and i want to use MyService in both of containers

But the wire function is created for a specific container strictly binds each service to the container.

In my case i act like this:

const createMyService = wire => class MyService {
  constructor () {
    wire(this, 'myDependency', TYPE.myDependency)
  }
};

But it looks more like a trick.

How can i properly create service for use it in any container?

Hey,

you could define a factory for one container which gets the service from another container.

containerB.bind(TYPE.myDependency).toFactory(() => containerA.get(TYPE.myDependency));

But the normal way is to define a dedicated wire for the "B" container if you want multiple containers and not to mix the dependencies accross containers.

You may think about to use only one container instead of multiple containers. In one of my application I use one container but multiple TYPE objects to seperate the services of each module from the core services.

@hbroer Thank you for your answer.

In my case i also want to move reusable services into npm package, for example MyService from initial comment.
In this case i want to bind different implementation to myDependency key depending on the app.

How can i properly create reusable services?

In case of muliple independent NPM Packages the best case would be to not depend on @owja/ioc within services nor, if avoidable, on other services. If it is not avoidable that Service A depends on Service B, you could pass B to A trough the constructor by the factory.

What I would absolutely not recommend but it is possible: You can pass the container to the service, same for the symbol and wire.

And you can also create a dedicated npm package with the container and wire only and import them where ever you need them (from the main application too):

import {Container, createWire} from "@owja/ioc";

const container = new Container();
const wire = createWire(container);

export {container, wire};

You might need to create the "TYPE Symbol" for the services just as a simple constant export const ServiceType = Symbol("MyServiceA");within the package and create a TYPE object in your main application with all imported symboles. You also need to bind all services, which should still done in the main application. Even if a service is only used in another service.

The problem here in general might be that you could build a dependency hell and get a few WTF moments if you have crossing dependencies over multiple packages.

btw if you only need different implementations (lets say ServiceA and ServiceB share the same interface but have a different implementation) and you want to choose one of them depending on your app (lets say appA and appB) then you only need different Bindings.

appA index.js:

import {container, TYPE} from "./services";
import {MyServiceA} from "@acme/my-service-a"

import {startApplication} from "./start";

// Bootstrapping
container.bind(TYPE.myDependency).to(MyServiceA).inSingletonScope();

// Starting the Main App
startApplication();

appB index.js:

import {container, TYPE} from "./services";
import {MyServiceB} from "@acme/my-service-b"

import {startApplication} from "./start";

// Bootstrapping
container.bind(TYPE.myDependency).to(MyServiceB).inSingletonScope();

// Starting the Main App
startApplication();

btw if you only need different implementations (lets say ServiceA and ServiceB share the same interface but have a different implementation) and you want to choose one of them depending on your app (lets say appA and appB) then you only need different Bindings.

I want to use service that have dependency myDependecy like this:

// app 1
import { MyService, TYPE } from "@some/lib";
import { Foo } from "./services/foo";

const container = new Container();

container.bind(TYPE.myService).to(MyService);
container.bind(TYPE.myDependency).to(Foo);
// app 2
import { MyService, TYPE } from "@some/lib";
import { Bar } from "./services/Bar";

const container = new Container();

container.bind(TYPE.myService).to(MyService);
container.bind(TYPE.myDependency).to(Bar);

And service from lib:

// there is an abstract ioc api
import { wire } from '@owja/ioc';

export class MyService {
  constructor() {
    wire(this, 'myDependency');
  }

  doSomething () {
    this.myDependency.something(); 
  }
}

UPD:
I understand that for this it is necessary to statically define a set of dependencies on the class itself

// app 1 bootstrap

import { MyService, MyServiceSymbol } from "@some/lib";
import { Foo } from "./services/foo";

const container = new Container();

const TYPE = {
  "myService": MyServiceSymbol;
}

container.bind(TYPE.myService).toFactory(() => new MyService(Foo));

export {container, TYPE};
// app 2 bootstrap

import { MyService, MyServiceSymbol } from "@some/lib";
import { Bar } from "./services/Bar";

const container = new Container();

const TYPE = {
  "myService": MyServiceSymbol;
}

container.bind(TYPE.myService).toFactory(() => new MyService(Bar));

export {container, TYPE};
// MyService  Lib

export class MyService {
  constructor(public myDependency) {}

  doSomething () {
    this.myDependency.something(); 
  }
}

I think that is the optimal way. Especially with plain javascript.

@hbroer thank you