Core Concepts
Understanding these 5 concepts is all you need to master godi.
1. Services
A service is any type that does something useful in your app.
// This is a service
type EmailSender struct {
apiKey string
}
func NewEmailSender() *EmailSender {
return &EmailSender{apiKey: "secret"}
}
func (e *EmailSender) Send(to, subject, body string) error {
// Send email...
return nil
}
Key Points:
Services are just regular Go types
No special interfaces or base types needed
If it does work, it’s probably a service
2. Constructors
A constructor is a function that creates a service. godi calls these for you.
// Simple constructor
func NewLogger() *Logger {
return &Logger{}
}
// Constructor with dependencies
func NewEmailService(sender *EmailSender, logger *Logger) *EmailService {
return &EmailService{
sender: sender,
logger: logger,
}
}
godi’s Magic: When you ask for EmailService, godi:
Sees it needs EmailSender and Logger
Creates those first (if needed)
Passes them to NewEmailService
Returns your ready-to-use service
3. Modules
A module groups related services together. Think of it as a package of functionality.
// Group email-related services
var EmailModule = godi.NewModule("email",
godi.AddSingleton(NewEmailSender),
godi.AddScoped(NewEmailService),
godi.AddScoped(NewEmailValidator),
)
// Group user-related services
var UserModule = godi.NewModule("user",
EmailModule, // Modules can include other modules!
godi.AddScoped(NewUserRepository),
godi.AddScoped(NewUserService),
)
Why Modules?
Organization: Keep related things together
Reusability: Use the same module in different apps
Clarity: See dependencies at a glance
4. Lifetimes
A lifetime controls when and how instances are created.
Singleton (One Forever)
// Created once, reused everywhere
godi.AddSingleton(NewDatabase)
// Everyone gets the same database connection
db1, _ := godi.Resolve[*Database](provider)
db2, _ := godi.Resolve[*Database](provider)
// db1 == db2 (same instance)
Use for: Databases, loggers, caches, configuration
Scoped (One Per Request)
// New instance for each scope
godi.AddScoped(NewShoppingCart)
// Different requests get different carts
scope1 := provider.CreateScope(ctx)
cart1, _ := godi.Resolve[*ShoppingCart](scope1.ServiceProvider())
scope2 := provider.CreateScope(ctx)
cart2, _ := godi.Resolve[*ShoppingCart](scope2.ServiceProvider())
// cart1 != cart2 (different instances)
Use for: Request handlers, repositories, business logic
5. Scopes
A scope creates a boundary for scoped services. Perfect for web requests!
func handleRequest(provider godi.ServiceProvider) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// Create scope for this request
scope := provider.CreateScope(r.Context())
defer scope.Close() // Clean up when done
// All scoped services in this request share the same instances
userService, _ := godi.Resolve[*UserService](scope)
cartService, _ := godi.Resolve[*CartService](scope)
// Both services might share the same transaction!
}
}
Real Example: Request with Database Transaction
// Transaction service (scoped)
type Transaction struct {
tx *sql.Tx
}
func NewTransaction(db *Database) *Transaction {
tx, _ := db.Begin()
return &Transaction{tx: tx}
}
// Repository uses the transaction
type UserRepository struct {
tx *Transaction
}
func NewUserRepository(tx *Transaction) *UserRepository {
return &UserRepository{tx: tx}
}
// In a request scope:
// 1. Transaction is created (once per scope)
// 2. All repositories in this scope use the SAME transaction
// 3. When scope closes, transaction commits/rollbacks
Quick Reference
Concept |
What It Is |
When to Use |
|---|---|---|
Service |
Any type that does work |
Everything in your app |
Constructor |
Function that creates a service |
One per service |
Module |
Group of related services |
Organize your app |
Singleton |
One instance forever |
Shared resources |
Scoped |
One instance per scope |
Request-specific |
Scope |
Boundary for scoped services |
Web requests |
Visual: How It All Works
1. Define Module
EmailModule = [EmailSender(singleton), EmailService(scoped)]
2. Build Provider
provider = Build(EmailModule)
3. Handle Request
scope = provider.CreateScope()
4. Resolve Service
service = Resolve[EmailService](scope)
// godi creates: EmailSender (if needed) → EmailService
5. Clean Up
scope.Close()
Common Patterns
Pattern 2: Request Context
// Scoped service that holds request data
type RequestContext struct {
UserID string
RequestID string
}
func NewRequestContext(ctx context.Context) *RequestContext {
return &RequestContext{
UserID: ctx.Value("userID").(string),
RequestID: ctx.Value("requestID").(string),
}
}
// Other services can use it
func NewAuditLogger(ctx *RequestContext, logger *Logger) *AuditLogger {
return &AuditLogger{ctx: ctx, logger: logger}
}
Pattern 3: Module Composition
// Small, focused modules
var LoggingModule = godi.NewModule("logging", ...)
var MetricsModule = godi.NewModule("metrics", ...)
var TracingModule = godi.NewModule("tracing", ...)
// Combine into larger module
var ObservabilityModule = godi.NewModule("observability",
LoggingModule,
MetricsModule,
TracingModule,
)
// Use in your app
var AppModule = godi.NewModule("app",
ObservabilityModule,
DatabaseModule,
BusinessModule,
)
Next Steps
Now that you understand the concepts:
Quick Start - See it all in action
Getting Started - Build something real
Module Patterns - Advanced techniques
Remember: Start simple, add complexity only when needed!