0.0.0-pre.0 • Published 5 years ago
service-composition v0.0.0-pre.0
Decorator-based Dependency Injection
Features
- Instance dependencies — Define service dependencies that already exist.
- Class dependencies — Define service dependency classes that are instantiated on demand.
- Optional dependencies — Define service dependencies that may be
nullif not in the container. - n-ary dependencies — Define multiple dependencies that may be resolved for a single
ServiceIdentifier. - Lazy dependencies — Create proxies for services that may have circularities.
- Constructor dependencies — Define dependencies that are injected as constructor arguments.
- Field dependencies — Define dependencies that are injected as fields after an dependency has been instantiated.
- Hierarchical dependency trees — Seperate reusable services with longer lifetimes from transient services.
Installation
npm install service-compositionUsage
NOTE: The following examples use TypeScript, but its possible to use
service-compositionin JavaScript (without decorators) as well. See below for examples
Step 1 - Define Services
ihelloservice.ts
import { ServiceIdentifier } from "service-composition";
const const IHelloService = ServiceIdentifier.create<IHelloService>("IHelloService");
export interface IHelloService {
sayHello(name: string): string;
}itranslateservice.ts
import { ServiceIdentifier } from "service-composition";
const const ITranslateService = ServiceIdentifier.create<ITranslateService>("ITranslateService");
export interface ITranslateService {
format(message: string, args: Record<string, any>): string;
}Step 2 - Define Implementations and Dependencies
helloservice.ts
import { IHelloService } from "./ihelloservice";
import { ITranslateService } from "./itranslateservice";
export class HelloService implements IHelloService {
constructor(@ITranslateService private translate: ITranslateService) {
}
sayHello(name: str) {
return this.translate.format("hello, {name}", { name });
}
}Step 3 - Wire up Services
app.ts
import { ServiceCollection } from "service-composition";
// service interfaces
import { IHelloService } from "./ihelloservice";
import { ITranslateService } from "./itranslateservice";
// concrete services (or mock services for testing)
import { HelloService } from "./helloservice";
const serviceCollection = new ServiceCollection();
// define factories
serviceCollection.setClass(IHelloService, HelloService);
// define instances
serviceCollection.setInstance(ITranslateService, {
format(message: string, args: Record<string, any>) {
return message.replace(/\{([\w\d_$]+)\}/g, (_, key) => key in args ? args[key] : _);
}
});
// create container
const serviceProvider = serviceCollection.createContainer();
// resolve dependencies and get instances
const helloService = serviceProvider.getService(IHelloService);
console.log(helloService.sayHello("Alice")); // prints: hello, AliceUsing in JavaScript (commonjs)
ihelloservice.js
const { ServiceIdentifier } = require("service-composition");
/** @type {ServiceIdentifier<IHelloService>} */
exports.IHelloService = ServiceIdentifier.create("IHelloService");
/**
* @typedef {IHelloService}
* @property {(name: string) => string} sayHello
*/itranslateservice.js
const { ServiceIdentifier } = require("service-composition");
/** @type {ServiceIdentifier<ITranslateService>} */
exports.ITranslateService = ServiceIdentifier.create("ITranslateService");
/**
* @typedef {ITranslateService}
* @property {(message: string, args: Record<string, any>) => string} format
*/helloservice.js
const { ITranslateService } = require("./itranslateservice.js")
class HelloService {
/**
* @param {import("./translateservice").ITranslateService} translate
*/
constructor(translate) {
this.translate = translate;
}
/**
* @param {string} name
*/
sayHello(name) {
return this.translate.format("hello, {name}", { name });
}
}
// Call decorator directly
ITranslateService(HelloService, undefined, /*parameterIndex*/ 0);
exports.HelloService = HelloService;app.js
const { ServiceCollection } = require("service-composition");
// service interfaces
const { IHelloService } = require("./ihelloservice");
const { ITranslateService } = require("./itranslateservice");
// concrete services (or mock services for testing)
const { HelloService } = require("./helloservice");
const serviceCollection = new ServiceCollection();
// define factories
serviceCollection.setClass(IHelloService, HelloService);
// define instances
serviceCollection.setInstance(ITranslateService, {
format(message, args) {
return message.replace(/\{([\w\d_$]+)\}/g, (_, key) => key in args ? args[key] : _);
}
});
// create container
const serviceProvider = serviceCollection.createContainer();
// resolve dependencies and get instances
const helloService = serviceProvider.getService(IHelloService);
console.log(helloService.sayHello("Alice")); // prints: hello, AliceAPI Overview
interface ServiceIdentifier— Identifies a service within the composition graph, and is a decorator that indicates the provided dependency is required.serviceName— Gets the (optional) name of the service.
namespace ServiceIdentifier— Methods used to get/createServiceIdentifierinstances.create(name?: string | symbol)— Creates a unique service identifier, which can act as a parameter or field decorator.
function optional(id)— A decorator that indicates the providedServiceIdentifieris an optional dependency.function many(id)— A decorator that indicates the providedServiceIdentifieris an n-ary dependency.enum ServiceDependencyCardinality— Describes the cardinality of aServiceDependency:ZeroOrOne— The dependency may have at most one service.ExactlyOne— The dependency may have exactly one service.ZeroOrMore— The dependency may have any number of services.
interface ServiceDependency— Describes a dependency on a class.id— TheServiceIdentifierthat satisfies this dependency.parameterIndex— The constructor parameter this dependency satisfies, if this describes a parameter.propertyName— The name of the property (i.e., field) this dependency satisfies, if this describes a property.cardinality— The cardinality of the dependency.
namespace ServiceDependency:store(id, target, parameterIndex, cardinality)— Stores information about aServiceDependencyfor a constructor parameter.store(id, target, propertyName, cardinality)— Stores information about aServiceDependencyfor a property/field on an instance.get(target)— Gets theServiceDependenciesfor a class.isParameterDependency(dependency)— Tests whether aServiceDependencyis for a constructor parameter.isPropertyDependency(dependency)— Tests whether aServiceDependencyis for an instance field/property.
class ServiceDescriptor— Describes how a service should be activated.static forClass(ctor, staticArguments, supportsDelayedInstantiation)— Creates aClassDescriptorfor a class with the provided bound arguments and whether the result can be created as aProxyfor circular dependencies.static forInstance(value)— Creates anInstanceDescriptorfor a value.abstract activate(dependencies)— Instantiates a service from the descriptor.
class ClassDescriptor— AServiceDescriptordescribing a class.bind(...args)— Binds additional static arguments to aClassDescriptor.activate(dependencies)— Instantiates a service from the descriptor.
class InstanceDescriptor— AServiceDescriptordescribing an instance.activate(dependencies)— Instantiates a service from the descriptor.
class ServiceCollection— A catalog that maps aServiceIdentifierto aServiceDescriptor.new ServiceCollection(entries?)— Creates a newServiceCollection.get size()— Returns the number of entries in the collection.has(id)— Returns whether aServiceIdentifieris present in the collection.get(id)— Gets theServiceDescriptorassociated with aServiceIdentifier.set(id, descriptor)— Sets theServiceDescriptoror an array ofServiceDescriptorobjects to use for aServiceIdentifier.setInstance(id, value)— Shorthand forcol.set(id, ServiceDescriptor.forInstance(value)).setClass(id, ctor, staticArguments, supportsDelayedInstantiation?)— Shorthand forcol.set(id, ServiceDescriptor.forClass(ctor, staticArguments, supportsDelayedInstantiation)).add(id, descriptor)— Adds aServiceDescriptoror an array ofServiceDescriptorobjects to use for aServiceIdentifier(similar tocol.set, except it appends to the list of descriptors).addInstance(id, value)— Shorthand forcol.add(id, ServiceDescriptor.forInstance(value)).addClass(id, ctor, staticArguments, supportsDelayedInstantiation?)— Shorthand forcol.add(id, ServiceDescriptor.forClass(ctor, staticArguments, supportsDelayedInstantiation)).createContainer(parent?)— Creates aServiceContainerwith an optional parent.
interface IServiceProvider— Describes the shape of a service provider.const IServiceProvider— AServiceIdentifierfor anIServiceProvider.class ServiceContainer— Instantiates and holds references to services described in aServiceCollection. EveryServiceContainerhas a singleIServiceProviderdependency of itself.new ServiceContainer(services)— Creates a newServiceContainerfrom aServiceCollection.createInstance(descriptor, ...args)— Instantiates aClassDescriptoror class with the provided static arguments.hasService(serviceId)— Tests whether the providedServiceIdentifiercan be instantiated by the container.getServices(serviceId)— Instantiates services for the providedServiceIdentifier(when not already instantiated) and returns an array of all instantiated services.getService(serviceId)— Instantiates the service for the providedServiceIdentifier(when not already instantiated) and returns it. Throws an error if there is not exactly one service for the providedServiceIdentifier.tryGetService(serviceId)— Instantiates the service for the providedServiceIdentifier(when not already instantiated) and returns it if it was defined. Throws an error if there is more than one service for the providedServiceIdentifier.createChild(services)— Creates a child container for this container's services and the providedServiceCollection.
Dependencies
This package depends on the following packages at runtime:
@esfx/iter-fn- For enhanced iteration.@esfx/lazy- For lazy initialization of classes.graphmodel- For managing the composition graph.tslib- For TypeScript runtime helpers.
License
This package is licensed under the MIT License.
Third Party License Notice
This package is partially based on the dependency injection system used by VS Code, but with substantial changes in implementation. Please review THIRD_PARTY_NOTICES for more information.
0.0.0-pre.0
5 years ago