Foundations
Service container & DI
The container is where dependency injection happens. It is the ephemeral context in practice: for each event it resolves the services your handlers ask for and hands them in. The domain declares what it needs; it never goes looking.
#Depend on names, not construction
The principle
When a class builds its own dependencies, it is welded to their concrete types and their wiring. When it declares them and receives them, it depends only on a contract. Inversion of control is what lets the same domain run against different implementations in different contexts.
In Stone.js
Register a service with @Service({ alias }) and it is bound under that name. Any handler or service that destructures the alias in its constructor receives the resolved instance. No new, no imports of implementations, no reflect-metadata.
import { Service } from '@stone-js/core'
@Service({ alias: 'tasks', singleton: true })
export class TaskService {
constructor ({ config }) { this.pageSize = config.get('stone.tasks.pageSize', 20) }
}import { defineService } from '@stone-js/core'
const TaskService = ({ config }) => ({
pageSize: config.get('stone.tasks.pageSize', 20)
})
export const services = [
defineService(TaskService, { alias: 'tasks', singleton: true }, true)
]Ask for what you need by name. The container decides how to build it, and when.
#Singletons are per-event
A singleton: true service is created once per container, which means once per event, not once per process. It is the natural default for services that are stateless within a request. Because the container is discarded after the event, singletons cannot leak between requests.
#Resolving explicitly
Destructured injection covers almost everything. When you need the container itself, for dynamic or conditional resolution, it is available and typed.
constructor ({ container }) {
this.tasks = container.resolve('tasks') // by alias
this.mailer = container.make(MailerService) // by class
}