Skip to content

Provider & DI System

Provider & DI System

flow8 uses the Gorgany framework’s provider pattern for dependency injection. All services are declared in provider structs, wired via struct tags, and initialized by the container at boot time.

How It Works

Dependency Declaration

Services declare their dependencies using the container:"inject" struct tag:

type FlowService struct {
MongoService *MongoService `container:"inject"`
PlayService *PlayService `container:"inject"`
LayerService *LayerService `container:"inject"`
CacheService *CacheService `container:"inject"`
AuditLogger *audit.Logger `container:"inject"`
}

The container resolves these at boot time — no manual wiring needed.

Init() Convention

If a service needs setup logic after injection, implement Init():

func (s *FlowService) Init() {
s.warmCache()
s.startBackgroundWorker()
}

Init() is called in dependency order — dependents are initialized before dependees.

Providers Registered in server.go

bootstrapper := &Bootstrapper{
Base: gorgany.NewBootstrapper(app),
}
bootstrapper.
AddProvider(&provider.AppProvider{}).
AddProvider(&gorgany.LoggerProvider{}).
AddProvider(&provider.RouteProvider{}).
AddProvider(&provider.AuditProvider{}).
AddProvider(&provider.JobProvider{}).
AddProvider(&gorgany.ViewProvider{}).
AddProvider(&provider.DbProvider{}).
AddProvider(&provider.McpProvider{}).
Bootstrap(container)

AppProvider (provider/provider_app.go)

Registers all application services: FlowService, PlayService, LayerService, ScheduleService, RetentionPolicyService, KVService, ChannelService, UserService, CompanyService, AIService, TestFixtureService, FlowTestService, ModuleMigrationService, ComponentService, AccessService, LinkService, and ~60 more.

LoggerProvider (Gorgany built-in)

Initializes zerolog structured logging to stdout (and optionally to file via ./storage/logs/).

RouteProvider (provider/provider_route.go)

Registers all HTTP routes by connecting controller methods to URL patterns via Gorgany’s router. Also registers WebSocket endpoint at /api/ws.

AuditProvider (provider/provider_audit.go)

Creates the audit logger with its sanitization configuration and injects it into services that need audit logging.

JobProvider (provider/provider_job.go)

Registers and starts gocron background jobs: CacheAuditFilters (every 3min) and ClearTTLCache (every 1min).

ViewProvider (Gorgany built-in)

Registers Go template rendering (used for email templates and any server-side rendered views).

DbProvider (provider/provider_db.go)

Establishes the MongoDB connection using MONGODB_URI and MONGODB_DB. Registers MongoService as a singleton. Also triggers app/module auto-registration on startup based on APPS_AUTOCREATE and APPS_FORCEUPDATE.

McpProvider (provider/provider_mcp.go)

Starts the MCP (Model Context Protocol) HTTP server on port 4445 using MCP_JWT_SECRET for authentication. Registers MCP tools that expose flow8 operations to AI agents.

Adding a New Service

  1. Create the service struct in pkg/service/:
pkg/service/my_service.go
package service
type MyService struct {
MongoService *MongoService `container:"inject"`
FlowService *FlowService `container:"inject"`
}
func (s *MyService) Init() {
// optional setup
}
func (s *MyService) DoSomething(id string) error {
// business logic
return nil
}
  1. Register it in AppProvider:
pkg/provider/provider_app.go
func (p *AppProvider) Register(container gorgany.Container) {
container.Singleton(func() *service.MyService {
return &service.MyService{}
})
// ... existing registrations
}
  1. Inject it wherever needed — the container handles the rest.

Lifecycle Order

server.go:
1. Create Gorgany app
2. Register providers (declare, don't execute)
3. Bootstrap(container):
a. Resolve dependency graph (topological sort)
b. Instantiate all singletons
c. Inject dependencies via struct tags
d. Call Init() in dependency order
4. Start HTTP server

Circular Dependency Avoidance

Gorgany’s container will panic at startup if a circular dependency is detected. To break cycles:

  • Use interfaces (pkg/intf/) instead of concrete types
  • Pass dependencies as function parameters rather than injected fields
  • Extract a shared utility into a third service both depend on