postgres

package
v0.0.0-...-2460f69 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 1, 2025 License: MIT Imports: 20 Imported by: 0

README

Generic Repository Pattern with Go Generics

This package implements a generic repository pattern using Go's generics feature. The implementation provides a more type-safe and reusable approach to data access compared to the traditional repository pattern.

Overview

The generic repository pattern in this package consists of the following components:

  1. Entity Interface: A common interface that all domain entities must implement, providing methods for ID access, timestamps, and soft deletion.

  2. Generic Repository Interface: A generic interface that defines common CRUD operations for any entity type.

  3. Base Repository: A generic implementation of the repository interface that provides common functionality for all entity types.

  4. Entity-Specific Repositories: Repositories that extend the base repository and add entity-specific functionality.

  5. Repository Factory: A factory that creates and manages repository instances.

Benefits of Using Generics

Using generics in the repository pattern provides several benefits:

  1. Type Safety: The compiler ensures that the correct entity types are used with the correct repositories, reducing runtime errors.

  2. Code Reuse: Common functionality is implemented once in the base repository and reused across all entity types.

  3. Reduced Boilerplate: Less code duplication for common operations like GetByID, Delete, List, and Count.

  4. Consistency: Ensures consistent behavior across different repositories.

  5. Flexibility: Allows for entity-specific customization while maintaining a common interface.

Usage

Creating a New Entity Type
  1. Define your entity struct in the domain package.
  2. Implement the Entity interface for your entity.
// Example: Implementing the Entity interface for a new entity
func (e *MyEntity) GetID() uuid.UUID {
    return e.ID
}

func (e *MyEntity) IsDeleted() bool {
    return e.DeletedAt != nil
}

// ... implement other required methods
Creating a New Repository
  1. Create a new repository struct that embeds the BaseRepository.
  2. Implement entity-specific methods like Create and Update.
  3. Provide a scan function that knows how to scan a database row into your entity.
  4. Provide a buildListQuery function that knows how to build SQL queries for your entity.
// Example: Creating a new repository for MyEntity
type MyEntityRepository struct {
    *BaseRepository[*domain.MyEntity]
    pool   *pgxpool.Pool
    logger *zap.Logger
}

func NewMyEntityRepository(pool *pgxpool.Pool, logger *zap.Logger) *MyEntityRepository {
    repo := &MyEntityRepository{
        pool:   pool,
        logger: logger,
    }

    baseRepo := NewBaseRepository[*domain.MyEntity](
        pool,
        logger,
        "postgres.my_entity_repository",
        "my_entities",
        repo.scanMyEntity,
        repo.buildListQuery,
    )

    repo.BaseRepository = baseRepo
    return repo
}

// Implement entity-specific methods...
Using the Repository Factory

The repository factory creates and manages repository instances. You can get repositories either as their specific types or as generic repositories:

// Get a repository as its specific type
parentRepo := factory.NewParentRepository()

// Get a repository as a generic repository
genericParentRepo := factory.GetGenericParentRepository()

Testing

This package includes test helpers that simplify integration testing of repositories. The test helpers are designed to follow best practices for testing in a hexagonal architecture:

  1. Separation of Concerns: Tests focus on the adapter implementation, not the application logic.
  2. Real Database Testing: Integration tests use a real PostgreSQL database to ensure the adapter works correctly with the actual database.
  3. Reusable Setup: Common setup code is extracted into helper functions to reduce duplication.
  4. Proper Cleanup: Resources are properly cleaned up after tests to avoid interference between tests.
Using Test Helpers

The package provides the following test helpers:

  1. SetupTestDatabase: Sets up a PostgreSQL connection for testing and returns a connection pool, context, logger, and cleanup function.
  2. SetupTestRepositories: Sets up repositories for testing and returns a repository factory, context, and cleanup function.

Important: Before running integration tests, ensure that a PostgreSQL server is running and accessible. The tests expect a PostgreSQL server to be running on localhost:5432 by default. You can use the provided Docker Compose file to start a PostgreSQL server:

docker-compose up -d postgresql

Example usage:

func TestMyRepository(t *testing.T) {
    // Skip if short flag is set
    if testing.Short() {
        t.Skip("Skipping integration test in short mode")
    }

    // Set up test repositories using the helper
    factory, ctx, cleanup := postgres.SetupTestRepositories(t)
    defer cleanup()

    // Get repositories from the factory
    repo := factory.NewMyRepository()

    // Test repository operations...
}

Best Practices for Hexagonal Architecture

When working with this package, follow these best practices to maintain a clean hexagonal architecture:

  1. Dependency Direction: Dependencies should always point inward, from adapters to the application core to the domain.
  2. Interface Segregation: Define small, focused interfaces in the ports package that are implemented by adapters.
  3. Domain Independence: The domain model should be independent of any external concerns, including the database.
  4. Adapter Isolation: Adapters should be isolated from each other and should only communicate through the application core.
  5. Testing at Boundaries: Test adapters at their boundaries to ensure they correctly implement the port interfaces.

By following these practices, you'll maintain a clean separation of concerns and a flexible, maintainable architecture.

Documentation

Overview

Package postgres provides PostgreSQL implementations of the repository interfaces. This file contains test helpers for PostgreSQL integration tests.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetTx

func GetTx(ctx context.Context) pgx.Tx

GetTx is a helper function to get the transaction from the context This is used by the repositories to get the transaction

func SetupTestDatabase

func SetupTestDatabase(t *testing.T) (*pgxpool.Pool, context.Context, *zap.Logger, func())

SetupTestDatabase sets up a PostgreSQL connection for testing. It returns a connection pool, context, logger, and cleanup function. The cleanup function should be deferred to ensure proper resource cleanup.

func SetupTestRepositories

func SetupTestRepositories(t *testing.T) (ports.RepositoryFactory, context.Context, func())

SetupTestRepositories sets up repositories for testing. It returns a repository factory, context, and cleanup function. The cleanup function should be deferred to ensure proper resource cleanup.

Types

type BaseRepository

type BaseRepository[T domain.Entity] struct {
	// contains filtered or unexported fields
}

BaseRepository is a generic base repository for PostgreSQL

func NewBaseRepository

func NewBaseRepository[T domain.Entity](
	pool *pgxpool.Pool,
	logger *zap.Logger,
	tracerName string,
	tableName string,
	scanFunc func(row pgx.Row) (T, error),
	buildListSQL func(filter ports.FilterOptions, sort ports.SortOptions) (string, []interface{}),
) *BaseRepository[T]

NewBaseRepository creates a new base repository

func (*BaseRepository[T]) Count

func (r *BaseRepository[T]) Count(ctx context.Context, filter ports.FilterOptions) (int64, error)

Count returns the total count of entities matching the filter

func (*BaseRepository[T]) Delete

func (r *BaseRepository[T]) Delete(ctx context.Context, id uuid.UUID) error

Delete marks an entity as deleted in the database

func (*BaseRepository[T]) GetByID

func (r *BaseRepository[T]) GetByID(ctx context.Context, id uuid.UUID) (T, error)

GetByID retrieves an entity by ID from the database

func (*BaseRepository[T]) List

func (r *BaseRepository[T]) List(ctx context.Context, options ports.QueryOptions) ([]T, *ports.PagedResult, error)

List retrieves a list of entities with pagination, filtering, and sorting

type ChildRepository

type ChildRepository struct {
	// contains filtered or unexported fields
}

ChildRepository implements the ports.ChildRepository interface for PostgreSQL

func NewChildRepository

func NewChildRepository(pool *pgxpool.Pool, logger *zap.Logger) *ChildRepository

NewChildRepository creates a new PostgreSQL child repository

func (*ChildRepository) Count

func (r *ChildRepository) Count(ctx context.Context, filter ports.FilterOptions) (int64, error)

Count returns the total count of children matching the filter

func (*ChildRepository) Create

func (r *ChildRepository) Create(ctx context.Context, child *domain.Child) error

Create creates a new child in the database

func (*ChildRepository) Delete

func (r *ChildRepository) Delete(ctx context.Context, id uuid.UUID) error

Delete marks a child as deleted in the database

func (*ChildRepository) GetByID

func (r *ChildRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.Child, error)

GetByID retrieves a child by ID from the database

func (*ChildRepository) List

List retrieves a list of children with pagination, filtering, and sorting

func (*ChildRepository) ListByParentID

func (r *ChildRepository) ListByParentID(ctx context.Context, parentID uuid.UUID, options ports.QueryOptions) ([]*domain.Child, *ports.PagedResult, error)

ListByParentID retrieves children for a specific parent with pagination, filtering, and sorting

func (*ChildRepository) Update

func (r *ChildRepository) Update(ctx context.Context, child *domain.Child) error

Update updates an existing child in the database

type GenericChildRepository

type GenericChildRepository struct {
	*BaseRepository[*domain.Child]
	// contains filtered or unexported fields
}

GenericChildRepository implements the ports.Repository interface for Child entities

func NewGenericChildRepository

func NewGenericChildRepository(pool *pgxpool.Pool, logger *zap.Logger) *GenericChildRepository

NewGenericChildRepository creates a new generic child repository

func (*GenericChildRepository) Create

func (r *GenericChildRepository) Create(ctx context.Context, child *domain.Child) error

Create creates a new child in the database

func (*GenericChildRepository) ListByParentID

func (r *GenericChildRepository) ListByParentID(ctx context.Context, parentID uuid.UUID, options ports.QueryOptions) ([]*domain.Child, *ports.PagedResult, error)

ListByParentID retrieves children for a specific parent with pagination, filtering, and sorting

func (*GenericChildRepository) Update

func (r *GenericChildRepository) Update(ctx context.Context, child *domain.Child) error

Update updates an existing child in the database

type GenericParentRepository

type GenericParentRepository struct {
	*BaseRepository[*domain.Parent]
	// contains filtered or unexported fields
}

GenericParentRepository implements the ports.Repository interface for Parent entities

func NewGenericParentRepository

func NewGenericParentRepository(pool *pgxpool.Pool, logger *zap.Logger) *GenericParentRepository

NewGenericParentRepository creates a new generic parent repository

func (*GenericParentRepository) Create

func (r *GenericParentRepository) Create(ctx context.Context, parent *domain.Parent) error

Create creates a new parent in the database

func (*GenericParentRepository) Update

func (r *GenericParentRepository) Update(ctx context.Context, parent *domain.Parent) error

Update updates an existing parent in the database

type GenericRepositoryFactory

type GenericRepositoryFactory struct {
	// contains filtered or unexported fields
}

GenericRepositoryFactory implements the ports.RepositoryFactory interface using generic repositories

func NewGenericRepositoryFactory

func NewGenericRepositoryFactory(ctx context.Context, connString string, logger *zap.Logger) (*GenericRepositoryFactory, error)

NewGenericRepositoryFactory creates a new generic repository factory

func (*GenericRepositoryFactory) Close

Close closes the connection pool

func (*GenericRepositoryFactory) GetGenericChildRepository

func (f *GenericRepositoryFactory) GetGenericChildRepository() ports.Repository[*domain.Child]

GetGenericChildRepository returns the generic child repository

func (*GenericRepositoryFactory) GetGenericParentRepository

func (f *GenericRepositoryFactory) GetGenericParentRepository() ports.Repository[*domain.Parent]

GetGenericParentRepository returns the generic parent repository

func (*GenericRepositoryFactory) GetTransactionManager

func (f *GenericRepositoryFactory) GetTransactionManager() ports.TransactionManager

GetTransactionManager returns the transaction manager

func (*GenericRepositoryFactory) InitSchema

func (f *GenericRepositoryFactory) InitSchema(ctx context.Context) error

InitSchema initializes the database schema

func (*GenericRepositoryFactory) NewChildRepository

func (f *GenericRepositoryFactory) NewChildRepository() ports.ChildRepository

NewChildRepository returns a child repository

func (*GenericRepositoryFactory) NewParentRepository

func (f *GenericRepositoryFactory) NewParentRepository() ports.ParentRepository

NewParentRepository returns a parent repository

type ParentRepository

type ParentRepository struct {
	// contains filtered or unexported fields
}

ParentRepository implements the ports.ParentRepository interface for PostgreSQL

func NewParentRepository

func NewParentRepository(pool *pgxpool.Pool, logger *zap.Logger) *ParentRepository

NewParentRepository creates a new PostgreSQL parent repository

func (*ParentRepository) Count

func (r *ParentRepository) Count(ctx context.Context, filter ports.FilterOptions) (int64, error)

Count returns the total count of parents matching the filter

func (*ParentRepository) Create

func (r *ParentRepository) Create(ctx context.Context, parent *domain.Parent) error

Create creates a new parent in the database

func (*ParentRepository) Delete

func (r *ParentRepository) Delete(ctx context.Context, id uuid.UUID) error

Delete marks a parent as deleted in the database

func (*ParentRepository) GetByID

func (r *ParentRepository) GetByID(ctx context.Context, id uuid.UUID) (*domain.Parent, error)

GetByID retrieves a parent by ID from the database

func (*ParentRepository) List

List retrieves a list of parents with pagination, filtering, and sorting

func (*ParentRepository) Update

func (r *ParentRepository) Update(ctx context.Context, parent *domain.Parent) error

Update updates an existing parent in the database

type RepositoryFactory

type RepositoryFactory struct {
	// contains filtered or unexported fields
}

RepositoryFactory implements the ports.RepositoryFactory interface for PostgreSQL

func NewRepositoryFactory

func NewRepositoryFactory(ctx context.Context, connString string, logger *zap.Logger) (*RepositoryFactory, error)

NewRepositoryFactory creates a new PostgreSQL repository factory

func (*RepositoryFactory) Close

func (f *RepositoryFactory) Close(ctx context.Context) error

Close closes the connection pool

func (*RepositoryFactory) GetTransactionManager

func (f *RepositoryFactory) GetTransactionManager() ports.TransactionManager

GetTransactionManager returns the transaction manager

func (*RepositoryFactory) InitSchema

func (f *RepositoryFactory) InitSchema(ctx context.Context) error

InitSchema initializes the database schema

func (*RepositoryFactory) NewChildRepository

func (f *RepositoryFactory) NewChildRepository() ports.ChildRepository

NewChildRepository returns a child repository

func (*RepositoryFactory) NewParentRepository

func (f *RepositoryFactory) NewParentRepository() ports.ParentRepository

NewParentRepository returns a parent repository

type TransactionManager

type TransactionManager struct {
	// contains filtered or unexported fields
}

TransactionManager implements the ports.TransactionManager interface for PostgreSQL

func NewTransactionManager

func NewTransactionManager(pool *pgxpool.Pool, logger *zap.Logger) *TransactionManager

NewTransactionManager creates a new PostgreSQL transaction manager

func (*TransactionManager) BeginTx

func (tm *TransactionManager) BeginTx(ctx context.Context) (context.Context, error)

BeginTx begins a new transaction and stores it in the context

func (*TransactionManager) CommitTx

func (tm *TransactionManager) CommitTx(ctx context.Context) error

CommitTx commits the transaction stored in the context

func (*TransactionManager) RollbackTx

func (tm *TransactionManager) RollbackTx(ctx context.Context) error

RollbackTx rolls back the transaction stored in the context

func (*TransactionManager) WithTx

func (tm *TransactionManager) WithTx(ctx context.Context, fn func(ctx context.Context) error) error

WithTx is a helper function to execute a function within a transaction If the function returns an error, the transaction is rolled back Otherwise, the transaction is committed

Directories

Path Synopsis

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL