The core philosophy of Demeter-DI is rooted in two distinct technical goals: adhering to the Law of Demeter (LoD) and providing a fluent, domain-specific language (DSL) for dependency management using the dsl-framework.
Why the Shift?
In our early technical explorations, we encountered significant bottlenecks when dealing with project scale. The shift to a pure Node.js architecture allowed us to implement:
- Asynchronous FS traversal: For non-blocking dependency resolution.
- Regex Buffering: To handle replacements and resolutions in memory before I/O.
- Atomic Operations: Ensuring that container builds don't leave the application in a partial state.
The DSL Framework: The Engine Room
The real power behind Demeter-DI isn't just the injection; it's the dsl-framework. This allows developers to chain methods like .define(), .compose(), and .create() into a readable, expressive narrative.
By using Proxy objects, we've removed the need to manually invoke service functions with parentheses when accessing them from the container.
Core Implementation Patterns
1. Define: Constants and Parameters
The define method is procedural and functional. It isolates constants from logic, promoting readability.
const container = containerFactoryFactory
.define('PI', 3.14)
.define('API_URL', '[https://api.example.com](https://api.example.com)')();
2. Compose: Lazy Singletons
compose creates a small DSL within your container. It utilizes Lazy Initialization—the service is executed only once, the first time it is accessed.
// The service is evaluated only when container.myService is called the first time.
containerFactoryFactory.compose('myService', (dep1, dep2) => dep1 + dep2, ['dep1', 'dep2']);
3. Create: The Factory Pattern
Unlike compose, create evaluates every time it is called. This is essential for resource-heavy operations or scenarios where a fresh instance is required (e.g., test fixtures).
// Useful for overriding complex services in test environments
containerFactoryFactory.create('complexService', () => ['fixture', 'data']);
Architectural Benefits: The Law of Demeter
Demeter-DI enforces the principle that an object should only interact with its immediate neighbors. By managing dependencies through a container, you decouple the "how" of service creation from the "what" of service usage.
- Loose Coupling: Services don't need to know how to instantiate their dependencies.
- Testability: Redefining a service with
.create()allows for instant mocking without touching the business logic. - Atomic Consistency: The closing function call
()triggers the final container build, ensuring all links are resolved.
Conclusion
By unifying the fluent interface of the dsl-framework with a strict DI container, we've built a tool that values developer ergonomics as much as architectural purity. It is built for a JS/TS world where clarity and performance must coexist.