microsoft / tsyringe

Lightweight dependency injection container for JavaScript/TypeScript
MIT License
5.11k stars 167 forks source link

Spring like @Configuration, @Bean #68

Open xenoterracide opened 4 years ago

xenoterracide commented 4 years ago

instead of calling .register to do multiple ... beans (for lack of a better term) that cannot be annotated with @injectable due to ownership, or interface-ness, it would be nice to be able to have spring like

@Configuration
class MyConfig {
     @Bean("...") dbConnector(): PostgresDatabaseConnector {
      ...
     }
....
}

note: in spring MyConfig also becomes a registered "bean".

Xapphire13 commented 4 years ago

Could you provide a bit more context on what a bean is? Do you have links to documentation or anything?

xenoterracide commented 4 years ago

https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html

a bean in java simply refers to any "managed object", it's a weird nomenclature but I haven't a better term off the top of my head (maybe a component), any arbitrary object the container can resolve. In this case what I'm looking for is a decentralized way of writing register calls, I imagine they would be providers...

so instead of writing

  container.register(Beans.APOLLO_SERVER, {
    useFactory: instanceCachingFactory((c) => {
      const config = c.resolve<Config>(Beans.APOLLO_CONFIG);
      return new ApolloServer({
        ...config,
        cors: {
          origin: process.env.CORS_ORIGINS!.split(','),
        },
      });
    }),
  });
... followed by a dozen or so similar registers

you could write, e.g. (note no direct reference to container is required, and having a big single method is discouraged)

@bean(Beans.APOLLO_SERVER)
appolloServer( c: DependencyContainer ) {
      return new ApolloServer({
        ...config,
        cors: {
          origin: process.env.CORS_ORIGINS!.split(','),
        },
      });
    }),
  });
}

bean is probably not a term you want to copy from the java world, I'm not sure what other containers use though.

MeltingMosaic commented 4 years ago

Does the @registry decorator meet your needs?

xenoterracide commented 4 years ago

I would say no, because it encourages a big block of code at the top of a class, instead of a big method, the problem more or less remains, things aren't broke up. Sure I suppose I could create a ton of classes.

xenoterracide commented 4 years ago

hmm... I thought for sure I commented on this (maybe it's on another ticket)... I have an implementation that is satisfactory but incomplete.

export function factory(explicit?: symbol, { singleton = true, container = rootContainer() } = {}) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const token = !!explicit ? explicit : Symbol.for(propertyKey);
    const factory = (c: DependencyContainer) => descriptor.value.apply(target, [c]);
    const finalFactory = singleton ? instanceCachingFactory(factory) : factory;
    container.register(token, { useFactory: finalFactory });
  };
}

and can then be used as follows

export default class StripeConfig {

  @factory(InjectToken.Stripe)
  stripe(c: DependencyContainer): Stripe {
    return new Stripe(c.resolve<string>(InjectToken.StripeSk));
  }
}
@registry([
  { token: InjectToken.ConfigStripe, useClass: StripeConfig }, // technically it doesn't *need* to be registered, but this would be the spring convention.
])

it's missing some spring capabilities of @Bean and @Configuration but I think that could be enhanced later.

if patches are welcome/desired I could try to do that here.

xenoterracide commented 4 years ago

@Xapphire13 any interest in a patch for this @factory? I've enhanced it a little bit more since I wrote this.

export function factory(
  explicit: symbol | constructor<any>,
  {
    singleton = true,
    container = rootContainer(),
  } = {},
) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const token =  typeof explicit === 'symbol' ? explicit : Symbol.for(propertyKey);
    const factory = (c: DependencyContainer) => descriptor.value.apply(target, [c]);
    const finalFactory = singleton ? instanceCachingFactory(factory) : factory;
    container.register(token, { useFactory: finalFactory });
    if (token !== explicit) {
      container.registerType(token, explicit);
    }
  };
}