Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Asynchronous Factories #12

Closed
osher opened this issue Feb 19, 2017 · 14 comments
Closed

Add support for Asynchronous Factories #12

osher opened this issue Feb 19, 2017 · 14 comments

Comments

@osher
Copy link

osher commented Feb 19, 2017

From your README.md example:

function Database({ connectionString }) {
  // We can inject plain values as well!
  this.conn = connectToYourDatabaseSomehow(connectionString);
}

This does not pass for me... 😢.
Dependency innitiations are often an asynchronous operation, especially when working with fail-fast policy. If a Peer cannot be found - I want the service to fail as soon as possible - preferably before it joins the load balancer.
For this I need my dependencies to be more eager-loaded and less lazy loaded.

Would you please consider support for

container.registerAsyncFunction( function(opts, next) )
container.regisgterPromiseFunction( function(opts) )

?

Thanks

@jeffijoe
Copy link
Owner

You want to inject a database connection which you optain with an async call, correct? 😊

@jeffijoe
Copy link
Owner

@osher I use this pattern with great success:

async function configureContainer () {
  // returns a promise
  const connection = await connectToDb()

  return createContainer()
    .registerValue({ connection })
}

// in some main file
configureContainer().then(container => {
  // You're ready!
})

At one point there was support for "async" registrations but it made the simpler usage way more complicated.

To summarize: create your container when you have everything you need. 😄

@jeffijoe
Copy link
Owner

Closing due to inactivity and an alternative pattern has been provided.

@amitport
Copy link

but how would you inject configuration parameters into connectToDb?

@jeffijoe
Copy link
Owner

@amitport depends on where you store them. I usually just do this:

async function configureContainer () {
  // returns a promise
  const connection = await connectToDb({
    host: process.env.DB_HOST,
    user: process.env.DB_NAME
  })

  return createContainer()
    .registerValue({ connection })
}

// in some main file
configureContainer().then(container => {
  // You're ready!
})

@amitport
Copy link

Well, it will be nice to store those get those parameter through awilix as well, but I understand that it is not possible (currently).

@jeffijoe
Copy link
Owner

Personally wouldn't do it that way since it's just config that you use only for that particular service.

@osher
Copy link
Author

osher commented Mar 27, 2017 via email

@amitport
Copy link

@osher, too blunt, IMO
@jeffijoe awilix can be improved, but it's very nice and useful all in all

@jeffijoe
Copy link
Owner

Sir, with all the respect - that's a very weak answer.

@osher that was not respectful at all.

I contributed of my time to let you know of a feature request.

I contributed my time to build this library and open source it for others to use free of charge, even MIT licensed.

If you don't intend to support it - don't tell me stories how to work around your limit - I had things done before awilix, you know?

I told you "stories to work around the limit" in an attempt to help you out. My answer is how async dependencies should be handled in Awilix.

I don't intend to support it not because it's impossible to implement, but because I feel it does not fit in a proper DI pipeline and would complicate usage for others using DI the intended way. It would make it impossible to use container.cradle.myService because it would now have to be a Promise.

The primary design goal of Awilix is that, if you chose to, you could throw it away and wire up your modules manually:

// no mention of awilix anywhere - good ol' constructor injection
const service1 = new Service1()
const service2 = new Service2()
const service3 = new Service3({
  service1: service1,
  service2: service2
})

we mean to give you a partial solution

It is not a partial solution.

Go initiate your modules on your own and then we'll cover whatever work is left

Do you mean that you would have to manually set up the dependencies that the async connectToDb needs? Not at all!

async function configureContainer () {
  const container = createContainer()
  container.registerValue('someDepThatDbNeeds', 42)

  // returns a promise
  const connection = await connectToDb(container.cradle)

  return container
    .registerValue({ connection })
}

// in some main file
configureContainer().then(container => {
  // You're ready!
})

@jeffijoe
Copy link
Owner

@amitport if you really want to store the config values in the container, you can use the approach in the comment above 😄

@gavinvangent
Copy link

gavinvangent commented Jan 10, 2023

Hi All,

I am faced with the same conundrum as @osher .. I need to resolve values after the container has been initialized.

I don't have much knowledge of the internals of this project, but I'd love to see a native solution implemented and look forward to your thoughts on this.

Here's an example of what I have:

container.register({
  sources: asValue<Source[]>([
    new PrimarySource(),
    new SecondarySource(),
  ]),
  loader: asFunction<Source[]>(async (someValue: string, sources: Source[]) => {
    for (let source of sources){
      const supported = await source.supports(someValue);
      if (supported) {
        return new Loader(source);
      }
    }
    
    throw new Error(`'${someValue}' is not supported`);
  }).scope()
})

At invocation time, I am given someValue:

const someValue = // get it from arguments/env/request/db/etc

const scope = container.createScope();
scope.register({
  someValue: asValue<string>(someValue)
});

const loader = scope.resolve<Loader>('loader');
// this loader is not the Loader instance, but instead, a promise that resolves a Loader instance, ie Promise<Loader>

Is there any reason not to implement a function which could resolve the promise before returning the value registered with the container? Example:

loader: asFunction<Source[]>(async (someValue: string, sources: Source[]) => {
  ...
  ...
}).await().scope()

Notice the .await() chained which would signal to the container to resolve the result of the function (given to AsFunction) as a promise.

Thanks for this great library and I look forward to hearing your thoughts.

@gavinvangent
Copy link

My guess is the library was built synchronously, and this would introduce asynchronicity which would be impossible to overcome without an entire rework of the base logic, and therefore, it would introduce breaking changes?

@jeffijoe
Copy link
Owner

You are correct that it would require the core to be async, which would incur a significant performance overhead for 99% of use cases that just use synchronous dependencies.

I suggest you use the pattern outlined above, or a factory instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants