# Adding New Repository Implementations for Database Operations in Golang Clean Architecture

> Learn to add new repository implementations in Golang clean architecture. Create adapters that fulfill interface contracts and wire them into the registry without altering business logic for seamless database operations.

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

---

**You add new repository implementations by creating an adapter package that fulfills the use-case layer's interface contract, then wiring it into the registry without modifying business logic.**

This guide demonstrates adding new repository implementations for database operations in the `manakuro/golang-clean-architecture` project. The codebase follows Clean Architecture principles where domain logic remains pure while data access details live in replaceable adapter layers. By adhering to the contract-first pattern, you can introduce MongoDB, PostgreSQL, or in-memory stores without altering use-case code.

## How the Repository Pattern Is Structured

The architecture enforces strict separation through four distinct layers. Each layer owns specific responsibilities and communicates through defined interfaces rather than concrete types.

### Layer Responsibilities

- **Domain Layer** ([`pkg/domain/model/user.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/domain/model/user.go)): Contains pure entities like the `User` struct with only JSON tags and zero external dependencies.
- **Use-Case Layer** ([`pkg/usecase/repository/user.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/usecase/repository/user.go)): Defines the **repository contract** via the `UserRepository` interface, declaring methods like `FindAll` and `Create` without implementation details.
- **Adapter Layer** ([`pkg/adapter/repository/user.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/adapter/repository/user.go)): Provides concrete data-access logic, such as GORM-based SQL operations, by implementing the use-case interfaces.
- **Registry** ([`pkg/registry/registry.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/registry/registry.go)): Handles dependency injection, mapping concrete adapter implementations to the abstract interfaces required by use-case services.

### The Contract-First Design

The `UserRepository` interface in [`pkg/usecase/repository/user.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/usecase/repository/user.go) establishes the boundary:

```go
type UserRepository interface {
    FindAll(u []*model.User) ([]*model.User, error)
    Create(u *model.User) (*model.User, error)
}

```

For transaction support, the `DBRepository` interface in [`pkg/usecase/repository/db.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/usecase/repository/db.go) provides a generic wrapper:

```go
type DBRepository interface {
    Transaction(func(interface{}) (interface{}, error)) (interface{}, error)
}

```

Business logic in the use-case layer depends only on these interfaces, remaining ignorant of whether data persists in MySQL, MongoDB, or memory.

## Step-by-Step Guide to Adding a New Repository

To add a MongoDB implementation alongside the existing GORM adapter, follow this contract-based workflow.

### 1. Create the Adapter Package

Create a new directory `pkg/adapter/repository/mongo` to house the MongoDB-specific implementation. This keeps the adapter layer modular and allows multiple storage engines to coexist.

### 2. Implement the UserRepository Interface

Define a struct that holds the MongoDB collection reference and implement all methods from the `UserRepository` interface:

```go
// pkg/adapter/repository/mongo/user.go
type mongoUserRepo struct {
    coll *mongo.Collection
}

func (r *mongoUserRepo) FindAll(_ []*model.User) ([]*model.User, error) {
    cursor, err := r.coll.Find(context.Background(), bson.D{})
    if err != nil {
        return nil, err
    }
    var users []*model.User
    if err = cursor.All(context.Background(), &users); err != nil {
        return nil, err
    }
    return users, nil
}

func (r *mongoUserRepo) Create(u *model.User) (*model.User, error) {
    _, err := r.coll.InsertOne(context.Background(), u)
    if err != nil {
        return nil, err
    }
    return u, nil
}

```

### 3. Expose a Factory Function

Provide a constructor that matches the signature expected by the registry, accepting infrastructure dependencies and returning the interface type:

```go
func NewMongoUserRepository(client *mongo.Client) repository.UserRepository {
    coll := client.Database("myapp").Collection("users")
    return &mongoUserRepo{coll: coll}
}

```

### 4. Wire Into the Registry

Modify [`pkg/registry/registry.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/registry/registry.go) to instantiate the new repository. Import the mongo adapter and update the `RegisterRepositories` function to accept the MongoDB client:

```go
import (
    mongoRepo "github.com/manakuro/golang-clean-architecture/pkg/adapter/repository/mongo"
)

func RegisterRepositories(db *gorm.DB, mongoClient *mongo.Client) *Registry {
    r := &Registry{}
    r.UserRepo = mongoRepo.NewMongoUserRepository(mongoClient)
    return r
}

```

### 5. Update the Entry Point

Adjust [`cmd/app/main.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/cmd/app/main.go) to initialize the MongoDB connection and pass it to the registry:

```go
mongoClient, err := mongo.Connect(context.Background(), options.Client().ApplyURI(cfg.MongoURI))
if err != nil {
    log.Fatal(err)
}
reg := registry.RegisterRepositories(gormDB, mongoClient)

```

The use-case services remain unchanged because they depend only on the `UserRepository` interface, not the concrete MongoDB type.

## Complete Working Example

After wiring the new repository, the application entry point constructs services that automatically use MongoDB without code changes in the business layer:

```go
package main

import (
    "context"
    "log"

    "github.com/manakuro/golang-clean-architecture/pkg/registry"
    "github.com/manakuro/golang-clean-architecture/pkg/usecase"
    "github.com/manakuro/golang-clean-architecture/pkg/domain/model"
)

func main() {
    // Registry initialized with MongoDB repository
    reg := registry.Init() 

    // Service uses UserRepository interface; implementation detail is invisible
    userSvc := usecase.NewUserService(reg.UserRepo)

    newUser := &model.User{Name: "Alice", Email: "alice@example.com"}
    created, err := userSvc.Create(context.Background(), newUser)
    if err != nil {
        log.Fatalf("create error: %v", err)
    }

    users, err := userSvc.ListAll(context.Background())
    if err != nil {
        log.Fatalf("list error: %v", err)
    }
    log.Printf("found %d users", len(users))
}

```

## Summary

- **Define contracts in the use-case layer**: Interfaces like `UserRepository` in [`pkg/usecase/repository/user.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/usecase/repository/user.go) abstract storage details from business logic.
- **Implement in the adapter layer**: Create new packages under `pkg/adapter/repository/` that fulfill these interfaces with specific technology code (GORM, MongoDB driver, etc.).
- **Wire through the registry**: Update [`pkg/registry/registry.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/registry/registry.go) to map concrete implementations to interfaces, keeping dependency injection centralized.
- **Preserve domain purity**: The `pkg/domain/model/` structs carry no import dependencies, ensuring business rules remain storage-agnostic.

## Frequently Asked Questions

### Can I maintain multiple repository implementations simultaneously?

Yes. The registry pattern allows you to instantiate both SQL and NoSQL repositories in [`pkg/registry/registry.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/registry/registry.go) and inject them into different use-case services. Simply create separate factory functions like `NewUserRepository` for GORM and `NewMongoUserRepository` for MongoDB, then assign each to the appropriate service constructor.

### How do I handle transactions when adding a new database type?

Implement the `DBRepository` interface from [`pkg/usecase/repository/db.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/usecase/repository/db.go) in your new adapter package. This interface requires a `Transaction` method that accepts a function wrapper. Your implementation must handle connection lifecycle management—whether through GORM's `Transaction` method or MongoDB's multi-document ACID sessions—while exposing the same atomicity contract to the use-case layer.

### Do I need to modify the domain models when changing storage engines?

No. Domain models in [`pkg/domain/model/user.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/domain/model/user.go) are pure structs with only JSON tags and validation logic. They contain no ORM-specific tags or database dependencies, allowing them to serialize to MongoDB BSON, SQL relational tables, or API JSON payloads without modification.

### How do I test a new repository implementation without running the full application?

Create an in-memory implementation of `UserRepository` in a test package. Implement `FindAll` and `Create` using a `map[string]*model.User` or slice backing store. Pass this stub to your use-case service in unit tests to verify business logic without database dependencies, then integration-test the concrete MongoDB or SQL adapter separately.