bowling-analysis-system
Version:
A comprehensive system for analyzing bowling techniques using video processing and metrics calculation
256 lines (217 loc) • 7.3 kB
JavaScript
/**
* Central registry for services with dependency injection
* @class ServiceRegistry
*/
class ServiceRegistry {
/**
* Creates an instance of ServiceRegistry
* @param {Object} options - Options
* @param {Object} [options.logger] - Logger instance
* @param {Object} [options.config] - Configuration object
*/
constructor(options = {}) {
this.logger = options.logger || console;
this.config = options.config || {};
this.services = new Map();
this.factories = new Map();
this.instances = new Map();
this.initializing = new Set();
this.initialized = new Set();
}
/**
* Register a service factory
* @param {String} name - Unique service name
* @param {Function} factory - Factory function that creates the service
* @param {Object} [options={}] - Service options
* @param {String[]} [options.dependencies=[]] - Service dependencies
* @param {Boolean} [options.singleton=true] - Whether service is a singleton
* @param {Boolean} [options.lazy=true] - Whether to initialize on first get
* @returns {ServiceRegistry} - For method chaining
*/
register(name, factory, options = {}) {
if (this.services.has(name)) {
this.logger.warn(`Service '${name}' already registered, overwriting`);
}
const serviceOptions = {
dependencies: options.dependencies || [],
singleton: options.singleton !== false,
lazy: options.lazy !== false,
...options
};
this.services.set(name, {
name,
factory,
options: serviceOptions
});
this.factories.set(name, factory);
return this;
}
/**
* Register a service instance directly
* @param {String} name - Unique service name
* @param {Object} instance - Service instance
* @returns {ServiceRegistry} - For method chaining
*/
registerInstance(name, instance) {
if (this.instances.has(name)) {
this.logger.warn(`Service instance '${name}' already registered, overwriting`);
}
this.instances.set(name, instance);
this.initialized.add(name);
return this;
}
/**
* Check if a service is registered
* @param {String} name - Service name
* @returns {Boolean} - True if service is registered
*/
has(name) {
return this.services.has(name) || this.instances.has(name);
}
/**
* Get a service instance
* @param {String} name - Service name
* @returns {Object} - Service instance
* @throws {Error} - If service is not registered or can't be created
*/
get(name) {
// Return existing instance if available
if (this.instances.has(name)) {
return this.instances.get(name);
}
// Check if service is registered
if (!this.services.has(name)) {
throw new Error(`Service not registered: ${name}`);
}
// Get service definition
const service = this.services.get(name);
// Check for circular dependencies
if (this.initializing.has(name)) {
throw new Error(`Circular dependency detected while initializing service: ${name}`);
}
// Create instance
try {
this.initializing.add(name);
// Resolve dependencies
const dependencies = {};
for (const depName of service.options.dependencies) {
dependencies[depName] = this.get(depName);
}
// Create instance
const instance = service.factory({
logger: this.logger,
config: this.config,
registry: this,
dependencies
});
// Store instance if singleton
if (service.options.singleton) {
this.instances.set(name, instance);
}
this.initialized.add(name);
return instance;
} finally {
this.initializing.delete(name);
}
}
/**
* Initialize all non-lazy services
* @returns {Promise<Map<String, Object>>} - Map of service name to instance
*/
async initializeServices() {
const promises = [];
for (const [name, service] of this.services) {
if (!service.options.lazy) {
promises.push((async () => {
try {
const instance = this.get(name);
// Call initialize method if available
if (instance && typeof instance.initialize === 'function') {
await instance.initialize();
}
return [name, instance];
} catch (error) {
this.logger.error(`Error initializing service '${name}':`, error);
throw error;
}
})());
}
}
const results = await Promise.all(promises);
return new Map(results);
}
/**
* Remove a service
* @param {String} name - Service name
* @returns {Boolean} - True if service was removed
*/
remove(name) {
const wasService = this.services.delete(name);
const wasInstance = this.instances.delete(name);
this.factories.delete(name);
this.initialized.delete(name);
return wasService || wasInstance;
}
/**
* Get all registered service names
* @returns {Array<String>} - Array of service names
*/
getServiceNames() {
return Array.from(new Set([
...this.services.keys(),
...this.instances.keys()
]));
}
/**
* Get all initialized service instances
* @returns {Map<String, Object>} - Map of service name to instance
*/
getInitializedServices() {
return new Map(this.instances);
}
/**
* Create a child registry that inherits services from this one
* @param {Object} [options={}] - Options for child registry
* @returns {ServiceRegistry} - Child registry
*/
createChildRegistry(options = {}) {
const childOptions = {
logger: options.logger || this.logger,
config: { ...this.config, ...(options.config || {}) }
};
const child = new ServiceRegistry(childOptions);
// Child can access parent services
const parentGet = this.get.bind(this);
child.get = function(name) {
if (child.instances.has(name)) {
return child.instances.get(name);
}
if (child.services.has(name)) {
return ServiceRegistry.prototype.get.call(child, name);
}
// Try to get from parent
return parentGet(name);
};
// Child can check parent services
const parentHas = this.has.bind(this);
child.has = function(name) {
return child.services.has(name) || child.instances.has(name) || parentHas(name);
};
return child;
}
/**
* Configure a service factory by name
* @param {String} name - Service name
* @param {Object} options - Configuration options
* @returns {ServiceRegistry} - For method chaining
*/
configure(name, options) {
if (!this.services.has(name)) {
throw new Error(`Cannot configure unknown service: ${name}`);
}
const service = this.services.get(name);
service.options = { ...service.options, ...options };
return this;
}
}
module.exports = ServiceRegistry;