How to Start Debugging Unfamiliar Code in the jFrame Go Framework
Start by locating the kernel package in core/kernel/engine.go and core/kernel/module.go, then trace the four-phase lifecycle (Init, Load, Start) through the example module to understand how dependencies flow through the injector.
When you inherit an unfamiliar Go codebase like jFrame (github.com/juanjitech/jframe), debugging effectively requires mapping the architectural backbone before diving into specific bugs. The framework follows a modular, dependency-injected design where every component revolves around a central kernel that orchestrates startup and shutdown. By understanding this contract first, you transform opaque stack traces into readable roadmaps of where configuration, initialization, or runtime logic fails.
Locate the Kernel: Your Entry Point for Debugging Unfamiliar Code
The kernel package acts as the central nervous system of jFrame. Every debugging session should begin here because it defines how modules are born, wired together, and executed.
The Engine Orchestrator (core/kernel/engine.go)
The Engine struct in core/kernel/engine.go is the primary runtime container. It initializes the dependency injector, creates a cancellable context for graceful shutdown, and manages the lifecycle of every registered module. When debugging startup failures, inspect the New() function to verify default configurations and the StartModule() method to see how the engine sequences module initialization.
The Module Contract (core/kernel/module.go)
All functionality in jFrame extends the Module interface defined in core/kernel/module.go. This interface mandates methods like Name(), Config(), Init(), Load(), and Start(). The file also provides UnimplementedModule, a helper struct that modules embed to satisfy the interface with no-ops. When you encounter a type assertion panic or missing method error, verify that the module correctly embeds this base struct.
Understand the Four-Phase Lifecycle Before Debugging
jFrame modules progress through a strict, ordered lifecycle. Understanding these phases helps you isolate whether a bug is a configuration error (phase 1), a dependency wiring issue (phases 2-3), or a runtime logic flaw (phase 4).
- Configuration Loading – The engine calls each module’s
Config()method and unmarshals Viper configuration into a dynamically generated struct. Silent failures here usually stem from mismatched YAML keys or struct tags. - PreInit → Init → PostInit – These sequential hooks allow modules to register dependencies in the injector (
h.Map), perform setup, and finalize configuration. Panics here often indicate missing dependencies or circular references. - Load – Modules retrieve required services (e.g., the HTTP router
*jin.Engine) from the injector usingh.Value,h.Invoke, orh.Load. This is where type mismatches surface. - Start – Each module runs its own goroutine. The engine waits for a graceful shutdown signal. Blocking operations or unhandled panics here will halt the entire service.
Trace Real Data Flow Through the Example Module
Concrete examples anchor abstract lifecycle concepts. The example module at mod/example/mod.go demonstrates the canonical pattern for dependency registration and retrieval.
Registering Dependencies with the Hub
During the Init phase, modules expose values through the dependency injector (referred to as the Hub). The example module registers a simple string:
func (m *Mod) Init(h *kernel.Hub) error {
// expose a string through the injector
h.Map("hello world")
return nil
}
When debugging "dependency not found" errors, verify that the providing module’s Init method successfully calls h.Map with the correct type.
Retrieving Dependencies in Load Phase
The Load phase demonstrates three equivalent methods for dependency retrieval:
func (m *Mod) Load(h *kernel.Hub) error {
// Method 1: Direct type-based lookup
s1 := h.Value(reflect.TypeOf("")).String()
// Method 2: Callback injection
h.Invoke(func(s string) {
fmt.Println("via Invoke:", s)
})
// Method 3: Pointer loading
var s2 string
h.Load(&s2)
fmt.Println(s1, s2)
return nil
}
If a module panics during Load, check that the type requested matches exactly what was registered in Init—the injector uses strict type matching.
Systematic Debugging Checklist for Unfamiliar Go Code
When a bug surfaces in jFrame, use this structured approach to isolate the failure point. Each step maps to specific source files and method signatures.
| Step | What to Inspect | Why It Matters |
|---|---|---|
| Engine creation | New in core/kernel/engine.go |
Confirms config defaults, injector setup, and context initialization |
| Module registration | RegMod in core/kernel/module.go |
Guarantees unique names and correct module ordering |
| Config unmarshalling | StartModule block that builds a dynamic struct |
Mis-named Viper keys cause silent failures where modules receive zero values |
| Dependency map | Calls to h.Map, h.Value, h.Load inside modules |
Missing or wrongly typed values trigger panics during Load or Start |
| Runtime errors | Start goroutine bodies in module implementations |
Anything that blocks or panics here stops the whole service; check for unhandled errors in goroutines |
When you encounter a stack trace, follow it back from the panic site to the module's implementation, then verify that the kernel properly injected all required dependencies before the failing method was called.
Summary
- Start at the kernel:
core/kernel/engine.goandcore/kernel/module.godefine the runtime contract and lifecycle hooks that every jFrame module follows. - Follow the four phases: Configuration → Init → Load → Start. Most bugs reveal themselves as configuration mismatches, missing dependencies, or runtime panics in these distinct stages.
- Trace the injector: The
Hubmethods (Map,Value,Load,Invoke) wire components together. Type mismatches here are the most common source ofLoad-phase panics. - Use the example module:
mod/example/mod.goprovides a working reference for how dependencies flow through a real implementation.
Frequently Asked Questions
What is the first file to check when debugging unfamiliar Go code in jFrame?
Start with core/kernel/engine.go. This file contains the Engine struct and its New() constructor, which initializes the dependency injector and configuration context. Understanding how the engine orchestrates the startup sequence provides the necessary context to interpret stack traces from any other part of the codebase.
How does jFrame's dependency injection help with debugging?
The explicit Hub interface in core/kernel/module.go forces dependencies to be declared in Init (via h.Map) and retrieved in Load (via h.Value or h.Load). This separation makes it easy to isolate whether a failure stems from a missing registration (check Init) or a type mismatch (check Load), rather than hunting through global state or hidden imports.
Where do runtime panics usually originate in jFrame modules?
Most runtime panics occur in the Load or Start phases. During Load, panics typically indicate that h.Value or h.Load was called for a type that was never registered in Init. During Start, panics usually happen inside the module's goroutine where actual business logic runs; unhandled errors here can crash the entire service because the engine waits for all modules to shut down gracefully.
How do I add logging to debug a custom module in jFrame?
Register a logger instance in your module's Init method using h.Map, then retrieve it in Load or Start via h.Load. Because the kernel guarantees that Init runs before Load, you can safely assume the logger is available when your module starts its goroutine. Alternatively, inspect the engine's configuration loading in core/kernel/engine.go to ensure your module's Config() struct tags match the YAML keys in your configuration file.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →