Галоўная / Артыкулы

Шаблон рэпазітарыя для масштабаваемых бэкендаў на Node.js

Шаблон рэпазітарыяў у Node.js: спрывяжыце Prisma за дапамою ін’ектаваных рэпазітарыяў і фабрыкі, каб службы заставалі тэставанымі, а транзакцыі — атамічнымі ў TypeScript.

Выдавец: REACTAPP.TOP4 хв чытання946 слоў

Крыніцы: Decoupling Data: Building Scalable Node.js Backends with the Repository Pattern

У ранніх версіях бэкендаў часта выкалічваюцца данні з базы дадзеных безпосередна з методаў служб. Гэта падходзіць для малых проектаў. Калі системам патрабуецца стабільнасць у працэ, жорсткая зв’язка межу правіламі бізнесу і вызовамі Prisma стае прычыной труднасцей у адтрыманні. Патэрн Repository — адна з способаў заставіць кодавую базу масштабаванай і практычной для тэстаў.

Проблема: “божыя службы”

Службы можу пачаць выглядаць так:

await prisma.user.findUnique({ where: { email } });
await prisma.user.create({ data });
// ... 100 more lines of database logic mixed with business rules ...

Спачатку все здаецца простым. З часам праблэмы стаюць виднымі:

  • Атрыманне дадзеных не па правілам: деталі, створаныя для SQL, прасачваюцца ў правілы бізнесу.
  • Дуплікацыя: тыя ж вызовы Prisma копіююцца між рознымі службамі.
  • Можлівасць тэстаў: для тэстав логіки патрабуецца жывая база дадзеных.
  • Шкодзябнасць: перэназначэнне табелі чы ўпраўлівай працюе на всі службы.

Это ўзгарнутая зв’язка: логіка рэўістрацыі чы справявання аутантыкацыі прыклеўана да Prisma. Адбіранне тэстаў без рэальнай базы дадзенаў становіцца практычна немагчымым. Спецыяльны шар доступу да дадзенаў — гэта тое, чаго не хапяе.

Рашэння: шаблон репазітарыю

Репазітарый выступае посереднікам межу бізнес-логікай і джерелам дадзенаў. Сэрвісы запрашаюць дадзеныя у репазітарыёў і не пакушаюцца з’ясаваць, чы ўтвараецца адпаведзь з MySQL, PostgreSQL чы іншага бэкенду. Сэрвісы перестаюць працаваць безпосередна з Prisma; яны вярбуюцца з репазітарыёў.

Канцэпцыя: сэрвісы должны керувацца тым, што яны атрымліваюць, а не тым, як гэта было атрымана.

Апісанне гэнерычнага кантракту

Спакойны інтерфейс падтрымлівае адносна стабільныя репазітарыю:

export interface IRepository<T> {
  findById(id: string): Promise<T | null>;
  create(data: any): Promise<T>;
  update(id: string, data: any): Promise<T>;
  delete(id: string): Promise<T>;
}

UserRepository (толькі логіка базы дадзенаў)

Задача рэпазітарыя — стабільна праця з дадзеннямі. Ён не должен ствараць Prisma; ён павінен прымоць кліента через канстрактар.

export class UserRepository {
  constructor(private prisma: PrismaClient) {}

  async findByEmail(email: string) {
    return this.prisma.user.findUnique({
      where: { email },
    });
  }

  async markEmailAsVerified(id: string) {
    return this.prisma.user.update({
      where: { id },
      data: { isEmailVerified: true },
    });
  }
}

Рэпазітарый не ўладае процесам стварэння Prisma — ён прымоць яго экземпляр.

Чаму неабходна передача PrismaClient (впрыск залежнасцяў)

Утримайцеся ад стварэння кліентаў унутрь рэпазітарыяў:

new PrismaClient(); // ❌ inside repository

Уместа таго выкарыстоўваюце впрыск:

constructor(private prisma: PrismaClient) {}

Чаму гэта важна

  • Адныя канекты: запобегае нехтарактным групам кліентаў.
  • Атомныя транзакцыі: калькі рэпазітарыяў можаюць дзеліцца адной транзакцыйнай сесыяй.
  • Магчымасць тэставання: кліент (або рэпазітары) можа быць імітаваны ў тэстах на елементарныя функцыі.

Гэты патэрн — впрыск залежнасцяў, стандартная практыка проектавання бэкенду.

Фабрыка сінглтон: адны источнік правды для рэпазітарыяў

Фабрыка рэпазітарыў з екзантамом і пацягнутым стварэнням дапамагае падтрымаць аптальнасць коду:

export class RepositoryFactory {
  private static instance: RepositoryFactory;
  private _userRepository?: UserRepository;

  private constructor(private prisma: PrismaClient) {}

  static getInstance(prisma: PrismaClient) {
    if (!this.instance) {
      this.instance = new RepositoryFactory(prisma);
    }
    return this.instance;
  }

  // Lazy Initialization: Only creates the repo if it's called
  public get userRepository(): UserRepository {
    if (!this._userRepository) {
      this._userRepository = new UserRepository(this.prisma);
    }
    return this._userRepository;
  }
}t

Сэрвісы пасля чаго вызываюць методы без викорыстання SQL:

this.userRepository.findByEmail(email);

Чаму екзантам? Адна фабрыка для всіх процэсаў означае наявнасць аднаго спільнага PrismaClient, відсутнасць дублікатных з’яўленняў і аднаковыя экзантамы рэпазітарыў.

Глэбокі прынт: атыматычныя транзакцыі

Процесы автанацыі часта выклікаюць патрэбу у багаторых кроках для падтрымкі цэласнасці. Калі корыстнік паўерывае свой электронны паштовы адрес, система можа апдэйтаваць данні корыстніка і адмахнуць OTP. І тое, і другое должны выйсці ўспэшна або ж оба неўспэшна.

Пакалі рэпазітарыяй выкорыстоўваюць аднаго кліента, транзакцыя можа ўключыць у сябе ўсія ўпрацоўванні:

await prisma.$transaction(async (tx) => {
  // We pass the transaction 'tx' into our factory
  const repos = RepositoryFactory.getInstance(tx);

  await repos.userRepository.markEmailAsVerified(userId);
  await repos.otpRepository.delete(otpId);
});

Якщо ў якомусь моменте ўсё зламаецца, Prisma анулюе весь процес — не застаёцца ніякога часткова выкарыстанага стану паверывання.

Чаму гэта важна:

  • Паверыванне і адмахненне OTP выконваюцца разам
  • У разе неўспэху ўсё анулюецца
  • Даны застаюць адносова стабільнымі
  • Раз'ядруванне

    Раней: службы залежалі безпасэчна ад Prisma.

    Пасля: службы залежаць ад рэпазітарыёў; рэпазітарыя залежаць ад PrismaClient.

    AuthService → UserRepository → Prisma
    

    Это значыць, што пазней можна заменіць Prisma без перапісву служб, бізнес-логіка застаецца чытабельной, а код базы дадзеных можа развівацца сама.

    Таяй суперсіла: можлівасць тэставання (мокінг)

    Калі запыткі хаваюцца ўнутрь служб, тэсты патрабуюць жывай базы дадзеных і стаюць медлівымі або ненадзеянымі. За дапамогою рэпазітарыёў уяднавальныя тэсты можу прыміць мокі.

    Замест MySQL пасункніце фальшывы UserRepository у AuthService:

    // Example: Testing the Register logic without a database
    const mockUserRepository = {
      findByEmail: jest.fn().mockResolvedValue(null), // Pretend user doesn't exist
      create: jest.fn().mockResolvedValue({ id: '123', email: 'test@test.com' })
    };
    
    // Now I can test the Service logic in isolation
    const authService = new AuthService(mockUserRepository as any);
    const result = await authService.register(userData);
    
    expect(result.user.email).toBe('test@test.com');
    expect(mockUserRepository.create).toHaveBeenCalled();
    

    Чаму гэта мае значэнне з тэхнічной точкі зору

    • Скорасць: можна запускаць тыячы тэстоў без додатковага навантажэння на сеть і базу дадзеных.
    • Надзяйнасць: краевыя прыклады, такія як перадача часу, лёгкая сімулявацыя праз тое, што мок-об’ект кідае аберанцыю.
    • Довярэннасць: пасля переработкі адразу прыходзіць адказ, калі логіка зменшваецца.

    Што навучае гэтая рэалізацыя

    1. Раздзеленне аспектаў: сэрвісы керуюць правіламі бізнесу; рэпазітарыя керуюць запытамі.
    2. Масштабавання: новыя домены (броніравання, аренда і т. п.) выконваюцца за аднаковым прыемам.
    3. Структура для практычнага выкарыстоўвання: падзея паспаўляе за шаблонамі, распространенымі ў большых системах.

    Заключныя меркі

    Перамешчанне Prisma за рэпазітарыямі — гэта не проста перакладчык файлаў. Сэрвісы становяцца яснейшымі, тэсты стаюць дешавейшымі, а бэкенд краща падготавлена да росту. Якщо вызовы Prisma ператвараюць сэрвісы на «бога-об’екты», то патэран рэпазітарыяў ёсць практычны наступны крок.