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.
Based on user clarification:
create_child_scope() should NOT see parent scope's local registrationsregister_scoped(typeof(RequestContext), request_context) - register an existing instanceregister_transient(typeof(RequestContext), () => new RequestContext()) - register with factory delegateresolve, but resolve_all returns bothclassDiagram
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
Add fields to Scope.vala to store scope-local registrations and factories:
// 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;
Create a lightweight registration approach for scope-local items. Since these are simpler than container registrations, we can use a minimal approach:
private class LocalRegistration : Registration {
public LocalRegistration(Type service_type, Scope scope) {
// Simple registration that only serves one type
}
}
Alternatively, reuse the existing Registration class but manage it separately within the Scope.
register_instance - Register an existing instance/**
* 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:
local_registrations and local_factoriesscoped_instances for immediate availabilityregister_transient - Register with factory delegate/**
* 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);
resolve_type and resolve_registrationCurrent flow:
New flow:
If not found, fall back to container registrations
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);
}
resolve_all_typeMerge results from both scope-local and container registrations:
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);
}
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.
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]
// 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();
}
local_registrations Catalogue to Scopelocal_factories Dictionary to Scoperegister_instance(Type, Object) on Scoperegister_transient(Type, owned FactoryDelegate) on Scoperesolve_type to check scope-local firstresolve_all_type to merge both sourcesresolve_local_registration if needed| File | Changes |
|---|---|
src/Scope.vala |
Add storage fields, registration methods, update resolution logic |
examples/ScopeRegistrationDemo.vala |
New example file demonstrating usage |
None - requirements have been clarified.