Что такое паттерн Repository и каково его назначение в Go-проектах?

Ответ

Паттерн Repository (Репозиторий) — это архитектурный паттерн, который изолирует слой доступа к данным от остальной части приложения (бизнес-логики). Его основная задача — предоставить интерфейс для работы с сущностями доменной модели, как будто они находятся в оперативной памяти, скрывая детали взаимодействия с конкретным хранилищем (базой данных, внешним API, файловой системой и т.д.).

В Go-проектах этот паттерн часто является частью "чистой" или гексагональной архитектуры.

Пример структуры:

// file: domain/user.go
type User struct {
    ID   int
    Name string
}

// file: internal/repository/user_repository.go

// 1. Интерфейс определяет контракт, не зависящий от реализации
type UserRepository interface {
    GetByID(ctx context.Context, id int) (*domain.User, error)
    Store(ctx context.Context, user *domain.User) error
}

// 2. Конкретная реализация для PostgreSQL
type postgresUserRepository struct {
    db *sql.DB
}

// Конструктор для создания репозитория
func NewPostgresUserRepository(db *sql.DB) UserRepository {
    return &postgresUserRepository{db: db}
}

// Реализация метода интерфейса
func (r *postgresUserRepository) GetByID(ctx context.Context, id int) (*domain.User, error) {
    // Логика выполнения SQL-запроса к PostgreSQL
    // ...
    return nil, nil
}

func (r *postgresUserRepository) Store(ctx context.Context, user *domain.User) error {
    // Логика INSERT или UPDATE
    // ...
    return nil
}

Ключевые преимущества:

  • Абстракция от источника данных: Бизнес-логика работает с интерфейсом UserRepository и не знает, хранятся данные в PostgreSQL, MongoDB или в памяти. Это позволяет легко менять хранилище данных, не изменяя бизнес-логику.
  • Улучшенная тестируемость: В unit-тестах для сервисного слоя можно легко подменить реальную реализацию репозитория на мок (mock), который имитирует поведение базы данных.
  • Централизация логики доступа к данным: Все запросы, связанные с определенной сущностью (например, User), собраны в одном месте. Это упрощает поддержку и отладку кода.

Ответ 18+ 🔞

А, паттерн Repository, говоришь? Ну, это ж классика, блядь! Такая хуйня, без которой в нормальном проекте — как без рук, а точнее, как без мозгов, нахуй.

Смотри, представь себе такую картину. У тебя есть бизнес-логика — это такая важная, умная часть, которая думает, что делать с пользователями, заказами, там, всякими сущностями. А есть база данных — это такая тёмная, сырая пещера, где эти сущности валяются в виде строчек и цифр. Так вот, если твоя логика полезет в эту пещеру сама, с голыми руками и криками «SELECT * FROM users WHERE id = ?», то это будет пиздец, блядь. Она вся перепачкается в SQL, нацепляет на себя зависимости от конкретной базы, и тестировать её будет невозможно, потому что для тестов надо будет городить целую боевую PostgreSQL, ёпта!

И вот тут на сцену выходит наш герой — Репозиторий. Это такой переводчик-охранник, сука. Он стоит между твоей чистой, прекрасной логикой и грязным, вонючим миром хранилищ данных. Логика говорит ему на человеческом языке: «Эй, дай-ка мне пользователя с ID 42». А репозиторий уже сам знает, как это выковырять: пойти в PostgreSQL, накатить JOIN'ов, сконвертировать строки в структуры — и подать результат на блюдечке. Бизнес-логика даже не подозревает, какая там, блядь, адская кухня творится под капотом!

Вот смотри на код, он тут как раз про это:

// file: domain/user.go
type User struct {
    ID   int
    Name string
}

Это наша сущность, доменный объект. Чистый, как слеза младенца, нихуя не знает про базы.

А дальше — магия, блядь:

// file: internal/repository/user_repository.go

// 1. Интерфейс — это святое! Контракт, договорённость.
type UserRepository interface {
    GetByID(ctx context.Context, id int) (*domain.User, error)
    Store(ctx context.Context, user *domain.User) error
}

Интерфейс! Это же, ёпта, главная фишка! Твоя бизнес-логика будет зависеть только от этого интерфейса. Ей похуй, что там внутри. Она просто знает, что некий объект умеет давать пользователя по ID и сохранять пользователя. И всё! А реализовать это можно хоть на палочках, хоть на облаках.

// 2. Конкретная реализация для PostgreSQL. Вот тут уже начинается грязь.
type postgresUserRepository struct {
    db *sql.DB
}

func NewPostgresUserRepository(db *sql.DB) UserRepository {
    return &postgresUserRepository{db: db}
}

func (r *postgresUserRepository) GetByID(ctx context.Context, id int) (*domain.User, error) {
    // А вот тут уже пошла конкретика: SQL, сканирование строк, ошибки подключения...
    // "SELECT id, name FROM users WHERE id = $1", и всё такое, блядь.
    return nil, nil
}

И что мы получаем в сухом остатке, а?

  • Абстракция, мать её! Захотел поменять базу с PostgreSQL на MongoDB? Пожалуйста! Пишешь новую реализацию mongodbUserRepository, которая тоже удовлетворяет интерфейсу UserRepository. И меняешь её в одном месте — там, где создаёшь репозиторий. Вся остальная логика даже не чихнёт, блядь! Она продолжает вызывать GetByID, а ей уже из монги данные прилетают. Красота!
  • Тестируемость — овердохуищная! Тестируешь сервис, которому нужен репозиторий? Да хули там! Пишешь мок (фейковую реализацию), которая возвращает тестовые данные, и не надо никаких реальных баз поднимать. Чисто, быстро, надёжно. Волнение ебать — ноль.
  • Порядок в доме. Вся логика работы с данными для одной сущности собрана в одном классе-репозитории. Не раскидана по всему проекту. Хочешь поменять запрос — идёшь в одно место. Не ищешь иголку в стоге SQL-строк, которые по всем хендлерам размазаны.

Короче, паттерн — пиздатый. Не используешь — сам себе враг, чувак. Это как построить дом без фундамента: вроде стоит, но первый же шторм его нахуй снесёт.