|
@@ -0,0 +1,241 @@
|
|
|
|
|
+# Plan: Scope-Local Registrations
|
|
|
|
|
+
|
|
|
|
|
+## Overview
|
|
|
|
|
+
|
|
|
|
|
+Add the ability to register items directly on a Scope, enabling per-scope dependency injection patterns such as injecting a `RequestContext` in web request handling scenarios.
|
|
|
|
|
+
|
|
|
|
|
+## Requirements Summary
|
|
|
|
|
+
|
|
|
|
|
+Based on user clarification:
|
|
|
|
|
+
|
|
|
|
|
+1. **Child scope isolation**: Child scopes created via `create_child_scope()` should NOT see parent scope's local registrations
|
|
|
|
|
+2. **Registration API**:
|
|
|
|
|
+ - `register_scoped(typeof(RequestContext), request_context)` - register an existing instance
|
|
|
|
|
+ - `register_transient(typeof(RequestContext), () => new RequestContext())` - register with factory delegate
|
|
|
|
|
+3. **Resolution priority**: Scope-local registrations take precedence over container registrations for `resolve`, but `resolve_all` returns both
|
|
|
|
|
+
|
|
|
|
|
+## Current Architecture
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+classDiagram
|
|
|
|
|
+ class Container {
|
|
|
|
|
+ -Catalogue~Type,Registration~ registrations
|
|
|
|
|
+ -Dictionary~Registration,Factory~ factories
|
|
|
|
|
+ -Dictionary~Registration,Object~ singletons
|
|
|
|
|
+ +register Type, FactoryDelegate
|
|
|
|
|
+ +create_scope Scope
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ class Scope {
|
|
|
|
|
+ -Container container
|
|
|
|
|
+ -Dictionary~Registration,Object~ scoped_instances
|
|
|
|
|
+ +resolve_type Type
|
|
|
|
|
+ +resolve_all_type Type
|
|
|
|
|
+ +create_child_scope Scope
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ class Registration {
|
|
|
|
|
+ +Type implementation_type
|
|
|
|
|
+ +Lifecycle lifecycle
|
|
|
|
|
+ +as_type Type
|
|
|
|
|
+ +with_lifecycle Lifecycle
|
|
|
|
|
+ +build
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ class Factory {
|
|
|
|
|
+ <<interface>>
|
|
|
|
|
+ +create Scope Object
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ Container --> Scope : creates
|
|
|
|
|
+ Scope --> Container : references
|
|
|
|
|
+ Container --> Registration : stores
|
|
|
|
|
+ Container --> Factory : stores
|
|
|
|
|
+ Registration --> Lifecycle
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Proposed Changes
|
|
|
|
|
+
|
|
|
|
|
+### 1. Add Scope-Local Storage
|
|
|
|
|
+
|
|
|
|
|
+Add fields to [`Scope.vala`](src/Scope.vala) to store scope-local registrations and factories:
|
|
|
|
|
+
|
|
|
|
|
+```vala
|
|
|
|
|
+// Scope-local registrations keyed by service type
|
|
|
|
|
+private Catalogue<Type, Registration> local_registrations;
|
|
|
|
|
+
|
|
|
|
|
+// Scope-local factories keyed by registration
|
|
|
|
|
+private Dictionary<Registration, Factory> local_factories;
|
|
|
|
|
+
|
|
|
|
|
+// Counter for generating unique registration IDs
|
|
|
|
|
+private int local_registration_counter;
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 2. Create Scope-Local Registration Class
|
|
|
|
|
+
|
|
|
|
|
+Create a lightweight registration approach for scope-local items. Since these are simpler than container registrations, we can use a minimal approach:
|
|
|
|
|
+
|
|
|
|
|
+```vala
|
|
|
|
|
+private class LocalRegistration : Registration {
|
|
|
|
|
+ public LocalRegistration(Type service_type, Scope scope) {
|
|
|
|
|
+ // Simple registration that only serves one type
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+Alternatively, reuse the existing [`Registration`](src/Registration.vala) class but manage it separately within the Scope.
|
|
|
|
|
+
|
|
|
|
|
+### 3. Add Registration Methods to Scope
|
|
|
|
|
+
|
|
|
|
|
+#### `register_instance` - Register an existing instance
|
|
|
|
|
+
|
|
|
|
|
+```vala
|
|
|
|
|
+/**
|
|
|
|
|
+ * Registers an existing instance in this scope.
|
|
|
|
|
+ *
|
|
|
|
|
+ * The instance will be returned when the specified service type is resolved.
|
|
|
|
|
+ * This registration is only visible within this scope, not in child scopes.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param service_type The type to register the instance as
|
|
|
|
|
+ * @param instance The instance to register
|
|
|
|
|
+ */
|
|
|
|
|
+public void register_instance(Type service_type, Object instance);
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+Implementation approach:
|
|
|
|
|
+- Create a simple Registration for the service type
|
|
|
|
|
+- Create a Factory that always returns the provided instance
|
|
|
|
|
+- Store in `local_registrations` and `local_factories`
|
|
|
|
|
+- Also store in `scoped_instances` for immediate availability
|
|
|
|
|
+
|
|
|
|
|
+#### `register_transient` - Register with factory delegate
|
|
|
|
|
+
|
|
|
|
|
+```vala
|
|
|
|
|
+/**
|
|
|
|
|
+ * Registers a transient factory in this scope.
|
|
|
|
|
+ *
|
|
|
|
|
+ * A new instance will be created each time the service type is resolved.
|
|
|
|
|
+ * This registration is only visible within this scope, not in child scopes.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param service_type The type to register
|
|
|
|
|
+ * @param factory_func The factory delegate to create instances
|
|
|
|
|
+ */
|
|
|
|
|
+public void register_transient(Type service_type, owned FactoryDelegate factory_func);
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 4. Modify Resolution Logic
|
|
|
|
|
+
|
|
|
|
|
+#### Update `resolve_type` and `resolve_registration`
|
|
|
|
|
+
|
|
|
|
|
+Current flow:
|
|
|
|
|
+1. Get registration from container
|
|
|
|
|
+2. Resolve based on lifecycle
|
|
|
|
|
+
|
|
|
|
|
+New flow:
|
|
|
|
|
+1. Check scope-local registrations first
|
|
|
|
|
+2. If found, use scope-local factory
|
|
|
|
|
+3. If not found, fall back to container registrations
|
|
|
|
|
+
|
|
|
|
|
+```vala
|
|
|
|
|
+public Object resolve_type(Type service_type) throws Error {
|
|
|
|
|
+ // Check scope-local first
|
|
|
|
|
+ if (this.local_registrations.has(service_type)) {
|
|
|
|
|
+ var registration = this.local_registrations.get_any(service_type);
|
|
|
|
|
+ return this.resolve_local_registration(registration, service_type);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Fall back to container
|
|
|
|
|
+ var registration = this.container.get_registration(service_type);
|
|
|
|
|
+ return this.resolve_registration(registration, service_type);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+#### Update `resolve_all_type`
|
|
|
|
|
+
|
|
|
|
|
+Merge results from both scope-local and container registrations:
|
|
|
|
|
+
|
|
|
|
|
+```vala
|
|
|
|
|
+public Enumerable<Object> resolve_all_type(Type service_type) {
|
|
|
|
|
+ var results = new List<Object>();
|
|
|
|
|
+
|
|
|
|
|
+ // Add scope-local instances first
|
|
|
|
|
+ foreach (var registration in this.local_registrations.get_or_empty(service_type)) {
|
|
|
|
|
+ results.append(resolve_local_registration(registration, service_type));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Add container instances
|
|
|
|
|
+ foreach (var registration in this.container.get_registrations(service_type)) {
|
|
|
|
|
+ results.append(resolve_registration(registration, service_type));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return Iterate.over(results);
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+### 5. Child Scope Behavior
|
|
|
|
|
+
|
|
|
|
|
+No changes needed to `create_child_scope()` - it already creates a fresh Scope with just a reference to the container. The new scope-local registration storage will be empty for child scopes, satisfying the isolation requirement.
|
|
|
|
|
+
|
|
|
|
|
+## Resolution Flow Diagram
|
|
|
|
|
+
|
|
|
|
|
+```mermaid
|
|
|
|
|
+flowchart TD
|
|
|
|
|
+ A[resolve_type called] --> B{Scope has local registration?}
|
|
|
|
|
+ B -->|Yes| C[Use scope-local factory]
|
|
|
|
|
+ C --> D{Lifecycle?}
|
|
|
|
|
+ D -->|Instance| E[Return cached instance]
|
|
|
|
|
+ D -->|Transient| F[Create new via factory]
|
|
|
|
|
+ B -->|No| G{Container has registration?}
|
|
|
|
|
+ G -->|Yes| H[Use container factory]
|
|
|
|
|
+ H --> I{Lifecycle?}
|
|
|
|
|
+ I -->|Singleton| J[Return container singleton]
|
|
|
|
|
+ I -->|Scoped| K[Return/create scoped instance]
|
|
|
|
|
+ I -->|Transient| L[Create new via factory]
|
|
|
|
|
+ G -->|No| M[Throw NOT_REGISTERED]
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Usage Example
|
|
|
|
|
+
|
|
|
|
|
+```vala
|
|
|
|
|
+// In a web server handler
|
|
|
|
|
+void handle_request(Request request) {
|
|
|
|
|
+ // Create a scope for this request
|
|
|
|
|
+ var scope = container.create_scope();
|
|
|
|
|
+
|
|
|
|
|
+ // Register request-specific context
|
|
|
|
|
+ var request_context = new RequestContext(request);
|
|
|
|
|
+ scope.register_instance(typeof(RequestContext), request_context);
|
|
|
|
|
+
|
|
|
|
|
+ // Register a transient factory for request-specific logger
|
|
|
|
|
+ scope.register_transient(typeof(RequestLogger), (s) => {
|
|
|
|
|
+ var ctx = s.resolve<RequestContext>();
|
|
|
|
|
+ return new RequestLogger(ctx.request_id);
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ // Resolve handlers - they will receive the request_context
|
|
|
|
|
+ var handler = scope.resolve<RequestHandler>();
|
|
|
|
|
+ handler.handle();
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+## Implementation Checklist
|
|
|
|
|
+
|
|
|
|
|
+- [ ] Add `local_registrations` Catalogue to Scope
|
|
|
|
|
+- [ ] Add `local_factories` Dictionary to Scope
|
|
|
|
|
+- [ ] Implement `register_instance(Type, Object)` on Scope
|
|
|
|
|
+- [ ] Implement `register_transient(Type, owned FactoryDelegate)` on Scope
|
|
|
|
|
+- [ ] Modify `resolve_type` to check scope-local first
|
|
|
|
|
+- [ ] Modify `resolve_all_type` to merge both sources
|
|
|
|
|
+- [ ] Add helper method `resolve_local_registration` if needed
|
|
|
|
|
+- [ ] Create example file demonstrating the feature
|
|
|
|
|
+- [ ] Add unit tests for scope-local registration behavior
|
|
|
|
|
+
|
|
|
|
|
+## Files to Modify
|
|
|
|
|
+
|
|
|
|
|
+| File | Changes |
|
|
|
|
|
+|------|---------|
|
|
|
|
|
+| [`src/Scope.vala`](src/Scope.vala) | Add storage fields, registration methods, update resolution logic |
|
|
|
|
|
+| [`examples/ScopeRegistrationDemo.vala`](examples/ScopeRegistrationDemo.vala) | New example file demonstrating usage |
|
|
|
|
|
+
|
|
|
|
|
+## Open Questions
|
|
|
|
|
+
|
|
|
|
|
+None - requirements have been clarified.
|