The reported issue claimed that services declared via ProvidesServices() were not available to dependent modules during their Init() phase, causing "service not found" errors.
After comprehensive testing and code analysis, I've determined that the service registration timing is working correctly in the current codebase. The issue appears to be a documentation problem rather than a code bug.
The framework processes modules in dependency order and for each module:
-
Inject Required Services (line 544 in application.go)
- Services declared via
RequiresServices()are resolved - Module constructor is called with injected services (if applicable)
- Services declared via
-
Initialize Module (line 557)
module.Init(app)is called- Module can access services via
app.GetService()
-
Register Provided Services (line 564)
- Services declared in
ProvidesServices()are registered - They become available for subsequent modules
- Services declared in
-
Next Module (loop continues)
- Process repeats for next module in dependency order
The framework already implements implicit dependency resolution! When a module declares:
func (m *MyModule) RequiresServices() []ServiceDependency {
return []ServiceDependency{
{
Name: "scheduler.provider",
Required: true,
},
}
}The framework automatically:
- Identifies which module provides "scheduler.provider"
- Adds an implicit dependency edge in the module graph
- Ensures the provider module initializes first
This is implemented in application.go:
addImplicitDependencies()(line 1054)addNameBasedDependencies()(line 1209)addNameBasedDependency()(line 1440)
The issue occurs when developers:
- Call
app.GetService()duringInit() - But DON'T declare the dependency via
RequiresServices()orDependencies()
Without explicit dependency declaration, modules initialize in alphabetical order, which may not match service dependency requirements.
Added clear documentation showing three approaches:
Option 1: RequiresServices() (Recommended)
func (m *MyModule) RequiresServices() []ServiceDependency {
return []ServiceDependency{
{Name: "scheduler.provider", Required: true},
}
}
func (m *MyModule) Init(app Application) error {
var scheduler *SchedulerModule
err := app.GetService("scheduler.provider", &scheduler)
// ...
}Option 2: Module Dependencies()
func (m *MyModule) Dependencies() []string {
return []string{"scheduler"}
}Option 3: Interface-Based Matching
func (m *MyModule) RequiresServices() []ServiceDependency {
return []ServiceDependency{
{
Name: "scheduler",
Required: true,
MatchByInterface: true,
SatisfiesInterface: reflect.TypeOf((*SchedulerModule)(nil)).Elem(),
},
}
}
⚠️ Important: If you access the scheduler service duringInit()usingapp.GetService()without declaring the dependency viaRequiresServices()orDependencies(), your module may be initialized before the scheduler module, causing a "service not found" error. Always declare service dependencies explicitly to ensure proper initialization order.
Created tests in service_registration_timing_test.go that validate:
- ✅ Services available during Init() with explicit Dependencies()
- ✅ Services available via RequiresServices() + Constructor pattern
- ✅ Implicit dependency ordering with RequiresServices() + Required:true
- ✅ Failure scenario when dependencies not declared
- ✅ Real-world scheduler + jobs module pattern
Created example in scheduler_dependency_example_test.go demonstrating:
- Correct usage of RequiresServices() with scheduler
- Automatic initialization order resolution
- Module registration order independence
All tests pass successfully:
=== RUN TestServiceRegistrationTiming
--- PASS: TestServiceRegistrationTiming (0.00s)
=== RUN TestServiceRegistrationTimingWithConstructor
--- PASS: TestServiceRegistrationTimingWithConstructor (0.00s)
=== RUN TestServiceRegistrationTimingWithoutDependencies
--- PASS: TestServiceRegistrationTimingWithoutDependencies (0.00s)
=== RUN TestServiceRegistrationTimingWithRequiresServices
--- PASS: TestServiceRegistrationTimingWithRequiresServices (0.00s)
=== RUN TestSchedulerServiceRegistrationTiming
--- PASS: TestSchedulerServiceRegistrationTiming (0.00s)
=== RUN TestSchedulerDependencyPattern
--- PASS: TestSchedulerDependencyPattern (0.00s)
The service registration timing issue described in the original report does not exist in the current codebase. The framework already:
- Registers services immediately after each module's Init() completes
- Creates implicit dependencies based on RequiresServices() declarations
- Initializes modules in correct dependency order
The actual problem was insufficient documentation about the requirement to declare service dependencies. This has been resolved by:
- Updating module documentation with clear examples
- Adding warnings about proper dependency declaration
- Creating comprehensive tests demonstrating correct usage
- Providing working examples of the dependency pattern
For developers using the framework:
- Always declare service dependencies using
RequiresServices()orDependencies() - Prefer RequiresServices() for service-level dependencies (more granular)
- Use Dependencies() for module-level dependencies (simpler but less flexible)
- Test module initialization order to ensure dependencies are resolved correctly
For framework maintainers:
- Consider adding runtime warnings when
GetService()is called during Init() for undeclared services - Add dependency graph visualization tools for debugging
- Improve error messages to suggest using RequiresServices() when service not found during Init()