Шаблон рэпазітарыя для масштабаваемых бэкендаў на Node.js
Шаблон рэпазітарыяў у Node.js: спрывяжыце Prisma за дапамою ін’ектаваных рэпазітарыяў і фабрыкі, каб службы заставалі тэставанымі, а транзакцыі — атамічнымі ў TypeScript.
Крыніцы: 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();
Чаму гэта мае значэнне з тэхнічной точкі зору
- Скорасць: можна запускаць тыячы тэстоў без додатковага навантажэння на сеть і базу дадзеных.
- Надзяйнасць: краевыя прыклады, такія як перадача часу, лёгкая сімулявацыя праз тое, што мок-об’ект кідае аберанцыю.
- Довярэннасць: пасля переработкі адразу прыходзіць адказ, калі логіка зменшваецца.
Што навучае гэтая рэалізацыя
- Раздзеленне аспектаў: сэрвісы керуюць правіламі бізнесу; рэпазітарыя керуюць запытамі.
- Масштабавання: новыя домены (броніравання, аренда і т. п.) выконваюцца за аднаковым прыемам.
- Структура для практычнага выкарыстоўвання: падзея паспаўляе за шаблонамі, распространенымі ў большых системах.
Заключныя меркі
Перамешчанне Prisma за рэпазітарыямі — гэта не проста перакладчык файлаў. Сэрвісы становяцца яснейшымі, тэсты стаюць дешавейшымі, а бэкенд краща падготавлена да росту. Якщо вызовы Prisma ператвараюць сэрвісы на «бога-об’екты», то патэран рэпазітарыяў ёсць практычны наступны крок.