Open
Description
Refactoring IOTA SDK to Uber‑FX
1. Background
Today the IOTA SDK hand‑wires constructors, context providers, and HTTP handlers via reflection (pkg/di
, H/Invoke
, composables). As modules grow (core, CRM, finance, warehouse, …) this “glue” becomes unwieldy:
- Every binary (server, migrate, seed, etc.) repeats plumbing.
- Dependencies aren’t explicit in function signatures.
- Lifecycle (start/stop hooks) is custom and ad hoc.
2. Why Uber‑FX?
fx
(built on dig
) offers:
- Automatic wiring of constructors (
fx.Provide
) - Module grouping (
fx.Options
) per bounded context - Lifecycle hooks (
fx.Lifecycle
forOnStart
/OnStop
) - Explicit dependencies in each constructor’s signature
- Built‑in introspection of your dependency graph
- Strong type‑safety and better testability
3. Key Benefits for IOTA SDK
- Modular initialization
- Each domain (core, CRM, finance, warehouse, website, …) becomes an
fx.Option
you can register independently.
- Each domain (core, CRM, finance, warehouse, website, …) becomes an
- Minimal boilerplate
- One
fx.New(...)
per binary—no more manual builder code.
- One
- Clear lifecycles
- Attach Postgres pool shutdown, WebSocket broker stop, HTTP server start/stop via
fx.Lifecycle
.
- Attach Postgres pool shutdown, WebSocket broker stop, HTTP server start/stop via
- Easier testing
- Swap out individual constructors (
fx.Testing
) or provide mocks viafx.Provide
.
- Swap out individual constructors (
- DDD‑friendly
- Your domain constructors remain pure (
func NewOrderService(repo OrderRepo) *OrderService
), but now wired automatically.
- Your domain constructors remain pure (
4. Proposed Architecture
4.1. Core Binary (cmd/server/main.go
)
package main
import (
"context"
"log"
"go.uber.org/fx"
"github.com/iota-uz/iota-sdk/internal/assets"
"github.com/iota-uz/iota-sdk/internal/server"
"github.com/iota-uz/iota-sdk/modules"
"github.com/iota-uz/iota-sdk/pkg/application"
"github.com/iota-uz/iota-sdk/pkg/configuration"
"github.com/iota-uz/iota-sdk/pkg/eventbus"
"github.com/iota-uz/iota-sdk/pkg/logging"
corefx "github.com/iota-uz/iota-sdk/fxmodules/core"
crmfx "github.com/iota-uz/iota-sdk/fxmodules/crm"
financefx "github.com/iota-uz/iota-sdk/fxmodules/finance"
warehousefx "github.com/iota-uz/iota-sdk/fxmodules/warehouse"
serverfx "github.com/iota-uz/iota-sdk/fxmodules/server"
)
func main() {
app := fx.New(
// —— Global Providers ——
fx.Provide(
configuration.New, // *Configuration, error
logging.NewLogger, // *logrus.Entry
func(cfg *configuration.Configuration) (*pgxpool.Pool, error) {
return pgxpool.New(context.Background(), cfg.Database.Opts)
},
eventbus.NewPublisher, // *eventbus.Publisher
application.New, // *application.Application
),
// —— Domain Modules ——
corefx.Module(),
crmfx.Module(),
financefx.Module(),
warehousefx.Module(),
// —— HTTP Server & Static Assets ——
serverfx.Module(),
// —— Application Initialization ——
fx.Invoke(func(app *application.Application) error {
// Load/Deregister built-in modules, register nav links & HashFS assets
if err := modules.Load(app, modules.BuiltInModules...); err != nil {
return err
}
app.RegisterNavItems(modules.NavLinks...)
app.RegisterHashFsAssets(assets.HashFS)
return nil
}),
)
app.Run() // Blocks until shutdown
}
4.2. Example fx.Module (e.g. CRM)
// fxmodules/crm/module.go
package crmfx
import "go.uber.org/fx"
func Module() fx.Option {
return fx.Options(
// Service + repo constructors
fx.Provide(
crmPersistence.NewClientRepository, // func NewClientRepository(...) ClientRepo
crmService.NewClientService, // func NewClientService(repo ClientRepo) *ClientService
presentation.NewClientController, // func NewClientController(svc *ClientService) *ClientController
),
// Register HTTP routes / GraphQL
fx.Invoke(presentation.RegisterClientRoutes),
)
}
4.3. HTTP Server Module
// fxmodules/server/module.go
package serverfx
import (
"context"
"log"
"go.uber.org/fx"
"github.com/iota-uz/iota-sdk/internal/server"
"github.com/iota-uz/iota-sdk/pkg/configuration"
)
func Module() fx.Option {
return fx.Options(
fx.Provide(
server.NewDefault, // func NewDefault(opts *server.Options) (*server.Server, error)
),
fx.Invoke(func(lc fx.Lifecycle, srv *server.Server, cfg *configuration.Configuration) {
lc.Append(fx.Hook{
OnStart: func(ctx context.Context) error {
log.Printf(">> Listening on %s", cfg.Address())
return srv.Start(cfg.SocketAddress)
},
OnStop: srv.Stop,
})
}),
)
}
5. Migration Roadmap
- Extract Core Constructors
- Move
NewConfig
,NewLogger
,NewPostgresDB
,NewEventPublisher
,application.New
intofx.Provide
‑compatible functions.
- Move
- Bootstrap
cmd/server
- Replace hand‑wired
main.go
with the fx-based pattern above.
- Replace hand‑wired
- Gradual Module Migration
- For each domain (core, CRM, finance, warehouse, website…):
- Create
fxmodules/<domain>/Module()
- Move all
NewXxx
constructors andRegisterRoutes
into it.
- Create
- For each domain (core, CRM, finance, warehouse, website…):
- Additional Binaries
- Apply same pattern to
cmd/migrate
,cmd/seed
,cmd/document
,cmd/collect‑logs
, wiring only the providers they need.
- Apply same pattern to
- Sunset
pkg/di
- Once all constructors live under fx, remove
pkg/di
andH/Invoke
.
- Once all constructors live under fx, remove
- CI & Testing
- Leverage
fx.Testing
to inject mocks. - Retain existing
NewXxx()
constructors for backward compatibility in unit tests.
- Leverage
6. Risks & Mitigations
- Startup‑time errors: missing providers surface at runtime.
Mitigation: enable fx’s graph printing (fx.New(fx.PrintDeps())
) in dev. - Learning curve for contributors.
Mitigation: provide a “Getting Started” doc + examples. - Longer build times when adding many constructors.
Mitigation: group into coarse‑grained modules; merge related constructors.
7. Next Steps
- Prototype
cmd/server
migration in a feature branch. - Update documentation and developer onboarding.
- Review and merge core infra changes.
- Roll out domain modules incrementally, validating with smoke tests.
By following this plan, IOTA SDK will gain a scalable, maintainable bootstrap system, clearer lifecycles, and faster developer onboarding—while preserving our DDD boundaries and testability.
Metadata
Metadata
Assignees
Type
Projects
Status
No status