ساختن یک مخزن پایه مقیاس پذیر با Typecript & Mongoose

هنگامی که شما روی رشد Node.js با MongoDB کار می کنید ، هر مجموعه ای – تگ ، یادداشت ها ، کاربران و غیره – به همان منطق اصلی CRUD نیاز دارد.
با این حال ، سیم کشی فیلترها ، صفحه بندی ، طرح ریزی ، مرتب سازی ، جلسات و جمعیت برای هر مدل ، Boilerplate City است.
در این پست ، ما بررسی خواهیم کرد کاربردی رویکرد به ایجاد یک مجرد و عمومی مخزن پایه کارخانه در Typescript که:
- تطبیق کردن تایپ قوی در مورد فیلترها ، پیش بینی ها و انواع
- حمایت صفحه بندیبا تصفیهبا مرتب سازیبا جمعیتوت جلسات
- است ، پسندیده-صفحه نمایش مکان نما ، جستجوی متن کامل ، یا تجمع در جاده
- کد شما را نگه می دارد خشک و تیم شما خوشحال
مشکل: انفجار دیگ بخار
یک معمولی getAll
روش برای TagDocument
ممکن است اینگونه به نظر برسد:
async getAll(userId: ID, session?: ClientSession): Promise<TagDocument[]> {
return unwrap(
await mongo.fire(() =>
tagModel.find({ user: userId }, null, { session })
)
);
}
اما پس از نیاز به:
- اضافه کردن صفحه بندی (
.skip()
/.limit()
) - اضافه کردن مرتب سازی (
.sort()
) - حمایت پیش بینی (
.find(filter, projection)
) - ضمیمه کردن جمعیت ()
- نخ از طریق مشتری
… شما در پایان این منطق را در هر مخزن کپی خواهید کرد. از همه بدتر ، روش شما بالون را با پارامترهای اضافی و اضافه بار امضا می کند.
راه حل: createBaseRepository
به جای اینکه خود را تکرار کنید ، ما می سازیم کارخانه:
import type {
ClientSession,
FilterQuery,
HydratedDocument,
Model,
PopulateOptions,
ProjectionType,
SortOrder,
} from "mongoose";
import { mongo } from "./mongo.config";
import { type CommandResult, unwrap } from "./global";
// 1) Pagination & sorting types
export interface PaginationOptions {
page?: number;
pageSize?: number;
}
export type SortBy<T> =
| Partial<Record<Extract<keyof T, string>, SortOrder>>
| [Extract<keyof T, string>, SortOrder][];
// 2) Fully-typed GetAll options
export interface GetAllOptions<T, Doc extends HydratedDocument<T>> {
filter?: FilterQuery<Doc>;
projection?: ProjectionType<Doc>;
pagination?: PaginationOptions;
sort?: SortBy<T>;
session?: ClientSession;
populate?: PopulateOptions | PopulateOptions[];
}
// 3) The factory function
export const createBaseRepository = <T, Doc extends HydratedDocument<T>>(
model: Model<T, {}, Doc>
) => ({
async getAll<F extends FilterQuery<Doc>>(
opts: GetAllOptions<T, Doc>
): Promise<Doc[]> {
const {
filter = {} as F,
pagination: { page = 1, pageSize = 10 } = {},
projection,
populate,
sort,
session,
} = opts;
let query = model.find(filter, projection ?? null, { session });
// Pagination
query = query.skip((page - 1) * pageSize).limit(pageSize);
// Sorting
if (sort) {
if (Array.isArray(sort)) {
query = query.sort(sort);
} else {
query = query.sort(sort as Record<string, SortOrder>);
}
}
// Population
if (populate) {
query = query.populate(populate);
}
// Execute & unwrap
const res = (await mongo.fire(() => query)) as CommandResult<Doc[]>;
return unwrap(res);
},
});
چگونه کار می کند
- انواع عمومی
-
T
رابط طرحواره خام شماست (به عنوان مثالNote { title: "string; … }
) -
Doc extends HydratedDocument
نوع واقعی سند Mongoose است.
GetAllOptions
-
filter
: از هر فیلتر سبک Mongoose استفاده کنید. -
projection
: شامل/حذف زمینه ها. -
pagination
: شماره صفحه و اندازه صفحه. -
sort
: مرتب سازی به شدت توسط هر زمینه طرحواره. -
session
: موضوع الفClientSession
برای معاملات -
populate
: جمعیت استاندارد مونگوز.
- عمل
- ما یک تک می سازیم
model.find()
پرس و جو ، سپس زنجیره ای.skip()
با.limit()
با.sort()
وت.populate()
بشر - ما
await mongo.fire(() => query)
(بسته بندی شما برای مدیریت اتصال) وunwrap()
نتیجه
گسترش repo پایه در رابط های خود
اگر برای مخازن خود از رابط هایی استفاده می کنید ، می توانید به راحتی شکل repo پایه را گسترش دهید:
export interface INotesRepository
extends ReturnType<typeof createBaseRepository<Note, NoteDocument>> {
// add any note-specific methods here
create(
title: "string,"
content: string,
user: string,
session?: ClientSession
): Promise<NoteDocument>;
}
سپس آن را پیاده سازی کنید:
export const notesRepository: INotesRepository = {
...createBaseRepository<Note, NoteDocument>(noteModel),
async create(title, content, user, session) {
const doc = new noteModel({ title, content, user });
const res = (await mongo.fire(() => doc.save({ session }))) as CommandResult<NoteDocument>;
return unwrap(res);
},
};
ضد آینده: جمع آوری و جستجوی متن
از آنجا که تمام پیکربندی پرس و جو شما در GetAlloptions زندگی می کند ، می توانید بعداً بدون تغییر امضای repo خود در مدل ها ، قابلیت های جدیدی را اضافه کنید. به عنوان مثال:
export interface GetAllOptions<T, Doc extends HydratedDocument<T>> {
filter?: FilterQuery<Doc>;
projection?: ProjectionType<Doc>;
pagination?: PaginationOptions;
sort?: SortBy<T>;
session?: ClientSession;
populate?: PopulateOptions | PopulateOptions[];
// new feature flags:
search?: string;
useCursor?: boolean;
aggPipeline?: PipelineStage[];
}
در داخل اجرای GetAll خود ، می توانید در Opts.Search شاخه کنید ، یک فیلتر جستجوی متن بسازید ، یا در صورت ارائه Agpipeline یک جمع را اجرا کنید – همه با همان روش. لایه خدمات شما ثابت می ماند و توسعه دهندگان شما یک مکان برای یادگیری و گسترش دارند.
فواید
- خشک و قابل نگهداری: همه منطق مشترک در یک مکان.
- کاملاً تایپ شده: TypeScript فیلترهای نامعتبر ، پیش بینی ها یا انواع مختلف را به خود جلب می کند.
-
ضد آینده: با گسترش پشتیبانی از صفحه نمایش مکان نما ، جستجوی متن کامل یا جمع آوری اضافه کنید
GetAllOptions
و کارخانه یک بار - API سازگار: هر روش مخزن همان سبک امضا را دارد.
پایان
با استفاده از یک عمومی ، کاربردی createBaseRepository
، شما یک پایه لاغر ، سازگار و آینده آماده برای همه مدل های Mongoose خود به دست می آورید. هیچ کپی بیشتر/چرت زدن. فقط یک بار بسازید ، در همه جا گسترش دهید و پایگاه کد خود را تمیز ، سریع و مقیاس پذیر نگه دارید.
اگر س questions ال دارید نظر خود را در زیر رها کنید! 💡
بیایید متصل شویم !!:
وابسته به لینکدین
لوب