# Understanding the Dependency Injection Registry Pattern in Go: A Complete Guide

> Master the dependency injection registry pattern in Go. Learn how to centralize your clean architecture setup with a single container for controllers, use cases, and repositories.

- Repository: [manato/golang-clean-architecture](https://github.com/manakuro/golang-clean-architecture)
- Tags: deep-dive
- Published: 2026-03-06

---

**The dependency injection registry pattern in Go creates a centralized container that wires together clean architecture layers by holding a single database connection and exposing factory methods to instantiate controllers, use cases, and repositories.**

This pattern is exemplified in the `manakuro/golang-clean-architecture` repository, where a lightweight DI container eliminates tight coupling between infrastructure and business logic. By isolating object creation in a dedicated registry package, the codebase achieves true separation of concerns while maintaining straightforward testability.

## What Is the Dependency Injection Registry Pattern?

The **dependency injection registry pattern** implements a centralized object factory that manages the lifecycle and wiring of application components. Unlike service locators that are called dynamically throughout the code, a registry initializes dependencies once during application startup and injects them into the appropriate layers.

In Go implementations, this pattern typically involves:

- A **registry struct** that holds shared infrastructure dependencies (such as database connections)
- **Factory methods** that instantiate concrete implementations of interfaces
- **Clean separation** between the creation logic and business logic

## How the Registry Pattern Works in golang-clean-architecture

The `pkg/registry` package in `manakuro/golang-clean-architecture` demonstrates a production-ready implementation that wires together four distinct layers: infrastructure, use case, adapter, and delivery.

### The Core Registry Structure

At the heart of the pattern lies a minimal struct that captures only the runtime dependencies required by the entire application. In [`pkg/registry/registry.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/registry/registry.go), the implementation stores a single `*gorm.DB` instance:

```go
type registry struct {
    db *gorm.DB
}

func NewRegistry(db *gorm.DB) Registry {
    return &registry{db}
}

```

This design ensures that database connection configuration happens once at startup and propagates consistently through all downstream components.

### Factory Methods for Clean Architecture Layers

The registry exposes methods that construct complete dependency chains for specific domains. The `NewUserController` method in [`pkg/registry/user.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/registry/user.go) illustrates how the pattern wires together multiple layers:

```go
func (r *registry) NewUserController() controller.User {
    u := usecase.NewUserUsecase(
        repository.NewUserRepository(r.db),
        repository.NewDBRepository(r.db),
    )
    return controller.NewUserController(u)
}

```

This factory method demonstrates **dependency inversion**: the controller depends on the use case interface, the use case depends on repository interfaces, but only the registry knows the concrete implementations.

### Wiring Dependencies in Main

The application entry point in [`cmd/app/main.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/cmd/app/main.go) orchestrates the initialization sequence by creating the registry and extracting the fully configured controller graph:

```go
db := datastore.NewDB()
defer db.Close()

r := registry.NewRegistry(db)
e = router.NewRouter(echo.New(), r.NewAppController())

```

This approach keeps [`main.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/main.go) free of complex wiring logic while ensuring that all components receive their dependencies before the HTTP server starts.

## Practical Implementation Examples

### Creating the DI Container in Production

When deploying the application, initialize the registry immediately after establishing infrastructure connections:

```go
package main

import (
    "github.com/manakuro/golang-clean-architecture/pkg/infrastructure/datastore"
    "github.com/manakuro/golang-clean-architecture/pkg/registry"
    "github.com/manakuro/golang-clean-architecture/pkg/infrastructure/router"
    "github.com/labstack/echo/v4"
)

func main() {
    config.ReadConfig()
    
    db := datastore.NewDB()
    defer db.Close()
    
    registry := registry.NewRegistry(db)
    appController := registry.NewAppController()
    
    e := echo.New()
    router.NewRouter(e, appController)
    e.Start(":8080")
}

```

### Mocking Dependencies for Testing

The registry pattern enables seamless testing by allowing you to substitute concrete implementations with mocks. Create a test registry that returns mock repositories instead of database-backed ones:

```go
type mockUserRepo struct{}
func (m *mockUserRepo) FindByID(id uint) (*model.User, error) {
    return &model.User{ID: id, Name: "Test"}, nil
}

type mockDBRepo struct{}
func (m *mockDBRepo) Transaction(txFunc func(interface{}) error) error {
    return txFunc(nil)
}

func TestUserController(t *testing.T) {
    // Manually construct the dependency chain with mocks
    mockUser := &mockUserRepo{}
    mockDB := &mockDBRepo{}
    
    uc := usecase.NewUserUsecase(mockUser, mockDB)
    ctrl := controller.NewUserController(uc)
    
    // Execute tests against ctrl with deterministic behavior
    user, err := ctrl.GetUser(1)
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    if user.Name != "Test" {
        t.Errorf("expected name 'Test', got %s", user.Name)
    }
}

```

### Adding a New Service to the Registry

When extending the application with a new domain (for example, an `Order` service), follow the established pattern to maintain consistency:

1. Create [`pkg/registry/order.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/registry/order.go) with a factory method:

```go
package registry

import (
    "github.com/manakuro/golang-clean-architecture/pkg/adapter/controller"
    "github.com/manakuro/golang-clean-architecture/pkg/usecase/usecase"
    "github.com/manakuro/golang-clean-architecture/pkg/usecase/repository"
)

func (r *registry) NewOrderController() controller.Order {
    o := usecase.NewOrderUsecase(
        repository.NewOrderRepository(r.db),
        repository.NewDBRepository(r.db),
    )
    return controller.NewOrderController(o)
}

```

2. Extend `AppController` in [`pkg/adapter/controller/app.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/adapter/controller/app.go):

```go
type AppController struct {
    User  controller.User
    Order controller.Order  // new field
}

```

3. Update `NewAppController` in [`pkg/registry/registry.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/registry/registry.go):

```go
func (r *registry) NewAppController() controller.AppController {
    return controller.AppController{
        User:  r.NewUserController(),
        Order: r.NewOrderController(),  // wire the new controller
    }
}

```

This approach ensures the new service integrates automatically without modifying existing business logic or infrastructure code.

## Benefits of Using a Registry for Dependency Injection

Implementing the **dependency injection registry pattern** in Go delivers several architectural advantages that align with clean architecture principles:

- **Strict Decoupling** – Business logic in use cases and delivery handlers depends only on interfaces defined in the domain layer. The registry is the sole location aware of concrete implementations, eliminating import cycles and layer violations.

- **Enhanced Testability** – By substituting the registry's factory methods or manually constructing dependency chains with mocks, you can test controllers and use cases in isolation without database dependencies or external services.

- **Centralized Configuration** – Database connections, cache clients, and other infrastructure resources are instantiated once in [`main.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/main.go) and passed to the registry, preventing connection leaks and configuration drift across the codebase.

- **Scalable Service Addition** – Adding new domains requires only creating a new factory method in the registry and updating the `AppController` aggregation, leaving existing code untouched and reducing regression risk.

## Summary

The **dependency injection registry pattern** in Go provides a lightweight, manual approach to wiring clean architecture components without heavy frameworks. Key takeaways include:

- The registry struct holds minimal runtime state (typically just a `*gorm.DB`) and exposes factory methods to construct concrete implementations
- Factory methods in files like [`pkg/registry/user.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/registry/user.go) wire together repositories, use cases, and controllers while maintaining interface-based dependencies
- The [`main.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/main.go) entry point initializes the registry once and extracts the fully configured `AppController` for the HTTP router
- Testing becomes straightforward by either mocking the registry or manually injecting mock implementations into the dependency chain
- New services integrate cleanly by adding factory methods without modifying existing business logic

## Frequently Asked Questions

### What is the difference between a registry and a service locator?

A **registry** initializes dependencies during application startup and injects them into constructors, whereas a **service locator** is called dynamically at runtime to look up dependencies. The registry pattern in `manakuro/golang-clean-architecture` promotes explicit dependency injection through factory methods, making dependencies visible in constructors and easier to trace than the hidden dependencies typical of service locators.

### How does the registry pattern improve testability in Go?

The registry pattern improves testability by centralizing concrete implementation choices in factory methods. During testing, you can replace these factory methods with versions that return mocks, or bypass the registry entirely to inject mock repositories and use cases directly into controllers. This approach allows unit tests to run without database connections or external services, as demonstrated in the `TestUserController` example that uses `mockUserRepo` and `mockDBRepo`.

### Can I use the registry pattern with interfaces other than GORM?

Yes, the registry pattern is database-agnostic. While `manakuro/golang-clean-architecture` uses `*gorm.DB` as the infrastructure dependency, you can adapt the pattern to hold any shared resource such as `*sql.DB`, Redis clients, MongoDB sessions, or cloud service clients. The key principle is that the registry holds concrete infrastructure instances and wires them into repository implementations that satisfy domain interfaces.

### Where should I initialize the registry in a Go application?

Initialize the registry in the [`main.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/main.go) entry point immediately after establishing infrastructure connections. In `manakuro/golang-clean-architecture`, this occurs in [`cmd/app/main.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/cmd/app/main.go) where the database connection is created, passed to `registry.NewRegistry()`, and the resulting `AppController` is handed to the router. This ensures all dependencies are wired before the HTTP server starts accepting requests and allows proper resource cleanup via `defer db.Close()`.