🛠️ Building Modular Go Services with Uber FX and SQLC

In the journey toward scalable, maintainable Go applications, few things matter more than clean architecture, lifecycle clarity, and domain-driven modularity. go-simple project exemplifies this evolution, transforming a basic Gin + Uber FX setup into a robust, idiomatic service with SQLC-backed persistence and domain-centric layering.

This article walks through the architectural decisions and implementation strategies that shaped the product module in, based on the commit.

In the previous article, we converted the entire project to Uber FX, thereby improving its modularity and making dependency management more systematic.

🧱 Domain-Centric Structure: Why It Matters

Rather than scattering controllers, services, and repositories across layer-centric folders, Mobin chose a domain-centric layout:

text
internal/
└── product/    
├── controller/    
├── service/    
├── repository/    
└── module.go

This structure encapsulates all business logic product in one place, making it easier to scale, test, and onboard new contributors.

⚙️ SQLC Integration: Declarative, Type-Safe Persistence

SQLC was introduced to generate type-safe Go code from raw SQL queries. The schema and query files live under:

text
internal/db/sqlc/
├── schema/
│   
└── schema.sql
├── queries/
│   
└── product.sql

The schema defines the products table, while the queries cover full CRUD operations. SQLC generates Go bindings that are injected into the repository layer.

🚀 Lifecycle-Driven Migrations

To ensure the database schema is applied before the server starts, Mobin integrated golang-migrate into the FX lifecycle:

go
fx.Invoke(migrate.RunMigrations)

This guarantees that migrations run automatically on startup, keeping the database in sync with the codebase.

🔌 Final Composition in main.go

The NewApp() function brings it all together:

go
func NewApp() *fx.App {
    return fx.New(fx.Provide(config.NewConfig, health.New, server.NewGinEngine,
    server.CreateHTTPServer, migrate.NewRunner, // ← migration runner),
    fx.Invoke(server.RegisterRoutes, server.StartHTTPServer, migrate.RunMigrations, // ← migration
    hook),)
}

This setup ensures that configuration, migrations, server startup, and domain modules are all orchestrated predictably.

Docker compose file

yaml
services:
  redis:
    image: redis:alpine
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    restart: unless-stopped
  postgres:
    image: postgres:latest
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=pass
      - POSTGRES_DB=database
    volumes:
      - postgres-data:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    restart: unless-stoppedvolumes:
  redis-data:
  postgres-data:

🧠 Lessons from the Refactor

  • Domain-centric design improves modularity and clarity.
  • Uber FX simplifies dependency injection and lifecycle management.
  • SQLC brings type safety and performance to database access.
  • Docker + Postgres + Redis provide a clean dev environment.
  • Migrations in FX ensure schema consistency without manual steps.

🧩 Conclusion

The evolution of go-simple from a basic Gin server into a modular, lifecycle-driven architecture reflects a deeper commitment to clarity, reliability, and idiomatic Go design. By embracing domain-centric structure, SQLC for type-safe persistence, and Uber FX for dependency injection and lifecycle orchestration.

This refactor isn’t just about cleaner code — it’s about building systems that think in terms of domains, evolve gracefully, and stay predictable under pressure. Whether you’re adding new modules, integrating middleware, or preparing for production, the patterns established here offer a blueprint for thoughtful Go development.

As go-simple continues to grow, it stands as a testament to architectural discipline and the power of iterative refinement. The journey from simplicity to elegance has only just begun.

Your Business — On AutoPilot with DDImedia AI Assistant
(Join Our Waitlist)

Visit us at DataDrivenInvestor.com

Join our creator ecosystem here.

DDI Official Telegram Channel: https://t.me/+tafUp6ecEys4YjQ1

Follow us on LinkedIn, Twitter, YouTube, and Facebook.