Что такое репозиторий (repository) в контексте ORM для Node.js?

Ответ

В контексте ORM (Object-Relational Mapping) для Node.js, репозиторий — это паттерн проектирования, который абстрагирует логику доступа к данным. Он действует как посредник между бизнес-логикой приложения и слоем данных (например, базой данных), предоставляя коллекцию-подобный интерфейс для работы с сущностями.

Основная цель: инкапсулировать все операции выборки, сохранения и фильтрации данных для конкретной сущности (например, User, Product) в одном месте.

Пример реализации репозитория с TypeORM (популярная ORM для Node.js/TypeScript):

  1. Определение сущности:
    
    // user.entity.ts
    import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity() export class User { @PrimaryGeneratedColumn() id: number;

@Column() email: string;

@Column() isActive: boolean; }


2.  **Создание кастомного репозитория:**
```typescript
// user.repository.ts
import { EntityRepository, Repository } from 'typeorm';
import { User } from './user.entity';

@EntityRepository(User)
export class UserRepository extends Repository<User> {
  // Кастомный метод для поиска активных пользователей
  async findActiveUsers(): Promise<User[]> {
    return this.createQueryBuilder('user')
      .where('user.isActive = :isActive', { isActive: true })
      .getMany();
  }

  // Метод для поиска по email с проверкой существования
  async findByEmailOrFail(email: string): Promise<User> {
    const user = await this.findOne({ where: { email } });
    if (!user) {
      throw new Error(`User with email ${email} not found`);
    }
    return user;
  }
}
  1. Использование в сервисе:
    
    // user.service.ts
    import { UserRepository } from './user.repository';

class UserService { constructor(private userRepository: UserRepository) {}

async getActiveUsers() { // Используем кастомный метод репозитория return await this.userRepository.findActiveUsers(); }

async createUser(userData: Partial) { // Используем стандартный метод save из Repository const user = this.userRepository.create(userData); return await this.userRepository.save(user); } }



**Преимущества использования репозиториев в Node.js:**
*   **Тестируемость:** Легко подменить реальный репозиторий моком в unit-тестах.
*   **Согласованность:** Все запросы к таблице `users` централизованы в одном классе.
*   **Изоляция бизнес-логики:** Сервисы не зависят от конкретной ORM или способа построения запросов.
*   **Повторное использование:** Сложные запросы, используемые в разных частях приложения, пишутся один раз.

В других ORM, таких как Prisma, паттерн репозитория часто реализуется вручную поверх сгенерированного клиента Prisma для достижения тех же целей абстракции и тестируемости.

Ответ 18+ 🔞

Слушай, я тут на днях разбирался с этими вашими репозиториями в Node.js. Ну, с ORM там, TypeORM. И знаешь, овердохуища всего начитался, пока понял, что к чему.

Короче, репозиторий — это такая хитрая жопа, которая сидит между твоей бизнес-логикой и базой данных. Представь себе: у тебя есть сервис, который юзерами рулит. И вместо того чтобы в каждом методе писать createQueryBuilder('user').where(...), ты всю эту муть с запросами засовываешь в один класс. И потом просто вызываешь методы типа findActiveUsers(). Красота же, ёпта!

Вот смотри, как это выглядит на практике. Берём TypeORM. Сначала сущность описываем — это как схема таблицы в коде.

// user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  email: string;

  @Column()
  isActive: boolean;
}

Ну, тут всё стандартно. id, email, флажок активный или нет. Ничего сложного.

А теперь самое интересное — делаем кастомный репозиторий. Это чтобы не пользоваться только тем, что TypeORM из коробки даёт.

// user.repository.ts
import { EntityRepository, Repository } from 'typeorm';
import { User } from './user.entity';

@EntityRepository(User)
export class UserRepository extends Repository<User> {
  // Кастомный метод для поиска активных пользователей
  async findActiveUsers(): Promise<User[]> {
    return this.createQueryBuilder('user')
      .where('user.isActive = :isActive', { isActive: true })
      .getMany();
  }

  // Метод для поиска по email с проверкой существования
  async findByEmailOrFail(email: string): Promise<User> {
    const user = await this.findOne({ where: { email } });
    if (!user) {
      throw new Error(`User with email ${email} not found`);
    }
    return user;
  }
}

Видишь? Всё, что связано с юзерами, теперь живёт тут. Хочешь активных — вызывай findActiveUsers(). Надо по email найти, да ещё и чтобы падало с ошибкой, если нет, — вот тебе findByEmailOrFail. Удобно, блядь! Не нужно в каждом сервисе одно и то же писать.

А теперь смотри, как этим в сервисе пользоваться. Просто сказка.

// user.service.ts
import { UserRepository } from './user.repository';

class UserService {
  constructor(private userRepository: UserRepository) {}

  async getActiveUsers() {
    // Используем кастомный метод репозитория
    return await this.userRepository.findActiveUsers();
  }

  async createUser(userData: Partial<User>) {
    // Используем стандартный метод save из Repository
    const user = this.userRepository.create(userData);
    return await this.userRepository.save(user);
  }
}

Сервис стал чистым, как слеза младенца. Никаких queryBuilder-ов на 10 строк. Всё спрятано в репозитории. И главное — тестировать это удовольствие теперь проще простого. В тестах подсовываешь мок репозитория и не паришься с подключением к реальной базе. Доверия к такому коду — ебать, выше крыши.

Ну и плюсов ещё вагон. Вся логика запросов в одном месте — не получится, чтобы в одном файле фильтр был isActive = true, а в другом active = 1. Изоляция полная: сервису похуй, как там внутри репозиторий запрос строит, через queryBuilder или сырой SQL. И если сложный запрос в трёх местах нужен — написал один раз в репозитории и юзай где хочешь. Экономия времени — просто пиздец!

Кстати, в других ORM, типа Prisma, такого из коробки нет. Там придётся этот паттерн самому поверх их клиента накатывать. Но цель та же — абстракция и тестируемость. Так что, если не хочешь потом охуевать от спагетти-кода, завязывай с прямыми запросами в сервисах и начинай пользоваться репозиториями. Говорю же — жизнь станет проще, ядрёна вошь!