رهگیرهای NestJS قابل استفاده مجدد با الگوی کارخانه

مقدمه
هرکسی که با NestJS در حال توسعه API ها باشد، مزایای منطق اتصال به نقاط انتهایی API با رهگیرها را دیده است. در حالی که رهگیرها یک ابزار عالی برای اضافه کردن اجرای کد قبل از نقاط پایانی شما هستند، آنها با مقداری سربار کد برای ایجاد یک رهگیر برای هر مورد در برنامه شما همراه هستند. من اخیرا متوجه شدم که رهگیرهایی برای موارد استفاده بسیار مشابه در یک NestJS API ایجاد می کنم و یک روش خوب و قابل استفاده مجدد برای ایجاد رهگیرها با استفاده از الگوی کارخانه ای کشف کردم که مقداری از نفخ کد را کاهش می دهد.
الگوی کارخانه بر قابلیت استفاده مجدد کد و کارایی تمرکز دارد. از یک کلاس انتزاعی به عنوان یک رابط مشترک در یک تابع کارخانه برای ایجاد یک رهگیر NestJS در حال پرواز به جای ایجاد یک رهگیر منفرد استفاده می کند.
در این پست فرض میکنم که شما تا حدودی درک اولیه از NestJS دارید. اگر با NestJS کاملاً تازه کار هستید، توصیه می کنم قبل از خواندن این مقاله، به اسناد، به ویژه بخش “نمای کلی” اسناد نگاهی بیندازید.
این الگو همچنین از TypeScript استفاده می کند و من فرض می کنم که شما با برخی از الگوهای اساسی TypeScript (عمومی، نحو کلاس، تایپ) آشنا هستید.
راه اندازی برنامه
برای تنظیم صحنه، با یک برنامه بسیار استاندارد NestJS شروع می کنیم. فرض کنید یک REST API با نقطه پایانی POST در داریم /user/create
جایی که سوابق کاربری جدید ایجاد می کنیم.
برای پیروی از الگوهای استاندارد NestJS، یک کنترلر، سرویس و ماژول خواهیم داشت.
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post('create')
createOneUser(@Body() dto: CreateOneUserDto) {
return this.userService.createOne(dto);
}
}
@Injectable()
export class UserService {
createOne(dto: CreateOneUserDto): string {
// User create stuff goes here...
return `User created successfully`;
}
}
@Module({
imports: [],
controllers: [UserController],
providers: [
UserService
],
exports: [],
})
export class UserModule {}
این یک راهاندازی بسیار استاندارد از یک ماژول NestJS است، بنابراین من به جزئیات کد بالا نمیپردازم.
حال، بگویید میخواهیم یک مرحله اعتبارسنجی به آن اضافه کنیم createOneUser
روش کنترلر قبل از اینکه با سرویس خود تماس بگیریم. این موردی است که معمولاً برای افزودن این مرحله اعتبار سنجی به یک رهگیر دست مییابد. الگوی استاندارد این است که یک رهگیر مستقل را پیاده سازی کنیم که می تواند برخی از منطق اعتبار سنجی را برای اجرا قبل از ایجاد یک کاربر جدید نگه دارد، اما اگر بخواهیم همان کار را در ماژول دیگری برای یک مدل موجودیت جداگانه انجام دهیم، چه؟ این معمولاً مستلزم ایجاد یک رهگیر دیگر برای انجام همان کار است. این مشکلی است که ما قصد داریم با الگوی کارخانه آن را حل کنیم.
NestJS راههای زیادی برای اتصال منطق به نقاط انتهایی شما، مانند لولهها و محافظها، ارائه میکند، اما برای این مثال، ما یک رهگیر با کارخانه خود ایجاد خواهیم کرد. امیدواریم ببینید که این الگو به راحتی می تواند برای لوله ها و حفاظ ها نیز دوباره ساخته شود.
ساخت یک کارخانه رهگیر
برای طراحی این الگو برای استفاده مجدد از کد، ما یک سرویس اضافی، یک سرویس اعتبار سنجی، که از رهگیر فراخوانی می شود، معرفی می کنیم. بنابراین رهگیر بیشتر شبیه یک گذر به سرویس اعتبار سنجی جدید عمل می کند. سپس جریان چیزی شبیه به این خواهد بود:
Interceptor -> Validation Service -> Controller -> Service
اکنون، برای شروع ساخت کارخانه خود، با ایجاد یک کلاس انتزاعی برای استفاده به عنوان یک رابط مشترک برای سرویس اعتبار سنجی خود شروع می کنیم.
export abstract class BaseValidationService {
abstract validateCreate(dto: T): T;
}
این یک کلاس انتزاعی به نام ایجاد می کند BaseValidationService
که می توانیم برای اطمینان از داشتن یک رابط مشترک برای سرویس اعتبار سنجی خود و برای سرویس های اعتبارسنجی آینده که ممکن است از این الگو استفاده کنند، گسترش دهیم. این به ما ایمنی نوع می دهد زیرا ما خدمات اعتبار سنجی خود را در کارخانه ادغام می کنیم. یک روش انتزاعی از validateCreate
تعریف شده است که می توانیم از آن برای نگه داشتن منطق اعتبارسنجی برای نقطه پایانی خود استفاده کنیم.
برای سرویس اعتبار سنجی، ما یک سرویس تزریقی جدید ایجاد خواهیم کرد که سرویس پایه ای را که به تازگی تعریف کردیم گسترش می دهد. ما می توانیم یک سرویس اعتبار سنجی مانند این ایجاد کنیم:
@Injectable()
export class UserValidationService extends BaseValidationService {
constructor() {
super();
}
validateCreate(body: CreateOneUserDto): CreateOneUserDto {
if (body.id.length < 5) {
throw new HttpException(
'id must be greater than 5 digits',
HttpStatus.BAD_REQUEST,
);
}
return body;
}
}
این با فرمت مطابقت دارد BaseValidationService
از طریق وراثت کلاس و IDE شما باید به شما اطلاع دهد اگر مشکلی در نحوه گسترش کلاس انتزاعی وجود دارد. را تعریف می کنیم validateCreate
تابعی که، برای مثال اهداف، آن مقدار را برای id
کلید در بدنه بزرگتر از 5 رقم است. این چیزی است که معمولاً می تواند با بسته ای مانند انجام شود class-validator
اما این فقط برای نشان دادن الگو است.
اکنون، در نهایت، برای ایجاد کارخانه، میتوانیم یک تابع کارخانه جدید بنویسیم که یک رهگیر ایجاد میکند که درخواست را به روش سرویس اعتبارسنجی که قبلاً تعریف کردیم، هدایت میکند.
export const ValidationCreateFactory = (
validation: new (...args: any[]) => BaseValidationService,
) => {
@Injectable()
class ValidationInterceptor implements NestInterceptor {
constructor(
@Inject(validation.name)
readonly validationService: BaseValidationService,
) {}
async intercept(
context: ExecutionContext,
next: CallHandler
): Promise> {
let body = context.switchToHttp().getRequest().body;
body = await this.validationService.validateCreate(body);
return next.handle().pipe();
}
}
return ValidationInterceptor;
};
تا یک قدم به عقب برگردیم و ببینیم این چه می کند. این تابعی است که کلاسی را به شکل کلاس انتزاعی ما می پذیرد BaseValidationService
و یک رهگیر NestJS را برمی گرداند. در داخل رهگیر، ما از طریق یک جریان نسبتا استاندارد از گرفتن بدنه درخواست HTTP و ارسال آن به عنوان یک آرگومان به validateCreate
روش در سرویس اعتبارسنجی
نکته خاصی که باید به آن توجه داشت این است که چگونه رهگیر می تواند نمونه سرویس اعتبار سنجی را از نمودار Nest DI بگیرد. این امر با انجام می شود @Inject
decorator، که در آن یک توکن برای سرویس اعتبارسنجی برای ایجاد وابستگی در روش سازنده آن استفاده میشود. این مهم خواهد بود زیرا ما سرویس اعتبار سنجی را به ماژول خود اضافه می کنیم که در آن باید از سینتکس طولانی برای ایجاد وابستگی ها استفاده کنیم.
@Module({
imports: [],
controllers: [UserController],
providers: [
UserService,
{
useClass: UserValidationService,
provide: UserValidationService.name,
},
],
exports: [],
})
export class UserModule {}
اکنون میتوانیم از تابع کارخانه استفاده کنیم تا همه چیز را در داخل کنترلر با هم قرار دهیم @UseInterceptors
دکوراتور بسیار شبیه به هر رهگیر معمولی است.
@Controller('user')
export class UserController {
constructor(private readonly userService: UserService) {}
@Post('create')
@UseInterceptors(ValidationCreateFactory(UserValidationService))
createOneUser(@Body() dto: CreateOneUserDto) {
return this.userService.createOne(dto);
}
}
اکنون ما یک الگوی قابل استفاده مجدد را پیاده سازی کرده ایم که می تواند یک مرحله اعتبار سنجی را به هر نقطه پایانی ایجاد در این برنامه Nest اضافه کند. در صورتی که مدلهای موجودیت دیگری به این API اضافه شوند، ماژول جداگانه میتواند به راحتی با ایجاد یک سرویس اعتبارسنجی جدید و استفاده از ValidationCreateFactory
تابعی که تعریف کردیم با نگاهی بیشتر، تصور کنید اگر نقطه پایانی بهروزرسانی وجود داشته باشد که بخواهیم بدنه یک نقطه پایانی بهروزرسانی جداگانه را تأیید کنیم. میتوانیم کارخانه دیگری ایجاد کنیم تا درخواست را به یک روش اعتبارسنجی جداگانه هدایت کنیم که میتوان آن را فراخوانی کرد validateUpdate
. اینجاست که شما واقعاً متوجه مزایای استفاده مجدد کد این الگو خواهید شد.
ملاحظات
یک نکته خاص در مورد این الگو وجود دارد. متوجه خواهید شد که کارخانه فقط یک تابع است، به این معنی که هر بار استفاده از آن معادل یک فراخوانی تابع جداگانه خواهد بود. در یک برنامه بزرگ، این می تواند منجر به برخی مشکلات عملکرد شروع سرد شود، زیرا هر تماس کارخانه با هر بار راه اندازی برنامه اجرا می شود. در اکثر برنامه های کوچک، این احتمالاً ناچیز است، اما در صورتی که الگویی مانند این را در برنامه خود اتخاذ کنید، نکته قابل توجهی است.
نتیجه گیری
امیدوارم این پست را دوست داشته باشید و چیزی از آن برداشته باشید. الگوهایی مانند این می توانند در هنگام استفاده از چارچوبی مانند NestJS که در آن ابزارهای مبتنی بر کلاس زیادی وجود دارد، بسیار خوب باشند. اشکال عملکرد این الگو را در نظر داشته باشید، اما اگر منطقی است از آن در برنامه خود استفاده کنید، امیدوارم موارد استفاده ممکن را بررسی کنید!
ممنون که خواندید :).