Categorygithub.com/sosalejandro/ddd-golang/pkg/repository
modulepackage
0.0.2-rc
Repository: https://github.com/sosalejandro/ddd-golang.git
Documentation: pkg.go.dev

# README

GenericRepository

The GenericRepository is a versatile and reusable component designed to manage aggregates within a Domain-Driven Design (DDD) architecture in Go. It leverages generics to accommodate any aggregate that implements the AggregateRootInterface, providing a consistent and scalable approach to data persistence and retrieval.

Table of Contents

Overview

The GenericRepository serves as a foundational component for handling the persistence of aggregate roots. By abstracting the storage mechanism, it enables developers to focus on the domain logic without worrying about the underlying data operations. This repository pattern facilitates a clean separation of concerns, promoting maintainability and scalability in complex applications.

Key Components

AggregateRootInterface

The AggregateRootInterface defines the contract that any aggregate root must adhere to. It ensures that the repository can interact with aggregates uniformly.

package aggregate

// AggregateRootInterface is an interface that represents an aggregate root.
type AggregateRootInterface interface {
	Id() uuid.UUID
	SetId(uuid.UUID)
	GetChanges() []RecordedEvent
	Load(ctx context.Context, events ...RecordedEvent) error
	Deserialize([]byte) error
	Serialize() ([]byte, error)
}

AggregateRepositoryInterface

The AggregateRepositoryInterface abstracts the data storage operations required by the GenericRepository. Any storage implementation (e.g., SQL, NoSQL, in-memory) must implement this interface to be compatible.

package repository

import (
    "context"
    "github.com/google/uuid"
    event_manager "github.com/sosalejandro/ddd-golang/pkg/event-manager"
)

type AggregateRepositoryInterface interface {
    SaveEvents(context.Context, []event_manager.EventPayload) error
    GetAggregateEvents(context.Context, uuid.UUID) ([]event_manager.EventPayload, error)
    LoadSnapshot(context.Context, uuid.UUID) (*Snapshot, error)
    SaveSnapshot(context.Context, *Snapshot) error
    Close() error
}

EventManager

The EventManager handles the registration and unmarshalling of domain events. It ensures that events are correctly processed and stored, maintaining the integrity of the event-driven architecture.

Usage

Initialization

To create a new instance of GenericRepository, provide it with an EventManager, an implementation of AggregateRepositoryInterface, and a factory function that returns a new instance of the aggregate.

import (
    event_manager "github.com/sosalejandro/ddd-golang/pkg/event-manager"
    "github.com/sosalejandro/ddd-golang/pkg/repository"
    "github.com/sosalejandro/ddd-golang/pkg/aggregate"
)

em := event_manager.NewEventManager()
repo := NewConcreteRepository() // Your implementation of AggregateRepositoryInterface
factory := func() aggregate.AggregateRootInterface {
    return &YourAggregate{}
}

genericRepo := repository.NewGenericRepository(em, repo, factory)

Saving Events

The SaveEvents method persists all uncommitted changes (events) of an aggregate.

ctx := context.Background()
aggregate := genericRepo.Load(ctx, aggregateID)
aggregate.SomeMethod()
err := genericRepo.SaveEvents(ctx, aggregate)
if err != nil {
    // Handle error
}

Rehydrating Aggregates

To reconstruct an aggregate's state, use the Rehydrate method, which loads events from the repository and applies them to the aggregate.

ctx := context.Background()
aggregate, err := genericRepo.Rehydrate(ctx, aggregateID)
if err != nil {
    // Handle error
}

Snapshot Management

Snapshots capture the state of an aggregate at a specific point in time, improving performance by reducing the number of events that need to be replayed.

  • Loading a Snapshot:

    aggregate, err := genericRepo.Load(ctx, aggregateID)
    if err != nil {
        // Handle error
    }
    
  • Saving a Snapshot:

    err := genericRepo.SaveSnapshot(ctx, aggregate)
    if err != nil {
        // Handle error
    }
    

Abstractions and Composition

The GenericRepository leverages abstraction and composition to decouple the domain logic from the persistence mechanism. By depending on interfaces (AggregateRepositoryInterface and AggregateRootInterface), it promotes flexibility and testability. This design allows developers to switch out or modify storage implementations without altering the core business logic.

Storage Implementations

Any storage backend (e.g., PostgreSQL, MongoDB, in-memory) can be integrated with the GenericRepository by implementing the AggregateRepositoryInterface. This ensures that the repository can persist and retrieve aggregates seamlessly, regardless of the underlying storage technology.

type InMemoryRepository struct {
    // Implementation details
}

func (r *InMemoryRepository) SaveEvents(ctx context.Context, events []event_manager.EventPayload) error {
    // Save events to in-memory store
}

func (r *InMemoryRepository) GetAggregateEvents(ctx context.Context, id uuid.UUID) ([]event_manager.EventPayload, error) {
    // Retrieve events from in-memory store
}

func (r *InMemoryRepository) LoadSnapshot(ctx context.Context, id uuid.UUID) (*Snapshot, error) {
    // Load snapshot from in-memory store
}

func (r *InMemoryRepository) SaveSnapshot(ctx context.Context, snapshot *Snapshot) error {
    // Save snapshot to in-memory store
}

func (r *InMemoryRepository) Close() error {
    // Close any resources if necessary
}

Generics and Flexibility

By utilizing Go's generics, the GenericRepository can accommodate any aggregate type that implements the AggregateRootInterface. This reduces boilerplate code and enhances reusability across different aggregates within the application.

type GenericRepository[T aggregate.AggregateRootInterface] struct {
    em      *event_manager.EventManager
    repo    AggregateRepositoryInterface
    factory func() T
}

Example

package main

import (
    "context"
    "fmt"
    "github.com/google/uuid"
    "github.com/sosalejandro/ddd-golang/pkg/aggregate"
    event_manager "github.com/sosalejandro/ddd-golang/pkg/event-manager"
    "github.com/sosalejandro/ddd-golang/pkg/repository"
)

// YourAggregate implements AggregateRootInterface
type YourAggregate struct {
    *aggregate.AggregateRoot
    // Your fields
}

func main() {
    em := event_manager.NewEventManager()
    repo := NewInMemoryRepository() // Your implementation
    factory := func() aggregate.AggregateRootInterface {
        return &YourAggregate{}
    }

    genericRepo := repository.NewGenericRepository(em, repo, factory)

    ctx := context.Background()
    aggregateID := uuid.New()

    // Rehydrate aggregate
    aggregate, err := genericRepo.Rehydrate(ctx, aggregateID)
    if err != nil {
        fmt.Println("Error:", err)
    }

    // Perform operations on aggregate
    // ...

    // Save events
    err = genericRepo.SaveEvents(ctx, aggregate)
    if err != nil {
        fmt.Println("Error:", err)
    }

    // Save snapshot
    err = genericRepo.SaveSnapshot(ctx, aggregate)
    if err != nil {
        fmt.Println("Error:", err)
    }

    // Close repository
    err = genericRepo.Close()
    if err != nil {
        fmt.Println("Error:", err)
    }
}

Conclusion

The GenericRepository provides a robust and flexible foundation for managing aggregates in a DDD-based Go application. By abstracting storage concerns and leveraging generics, it facilitates the development of scalable and maintainable systems. Whether you're integrating with SQL databases, NoSQL stores, or in-memory solutions, the GenericRepository adapts seamlessly, empowering developers to focus on delivering rich domain functionalities.

# Functions

No description provided by the author
No description provided by the author

# Structs

GenericRepository is a generic repository for aggregates.
No description provided by the author

# Interfaces

No description provided by the author