DiscoveryService के साथ गतिशील प्रदाता पंजीकरण
प्लगइन प्रणालियों के लिए DiscoveryService और MetadataScanner से रनटाइम पर प्रदाताओं को स्कैन और जोड़िए।
DiscoveryService के साथ गतिशील प्रदाता पंजीकरण, CoddyKit पर NestJS एंटरप्राइज़ बैकएंड API का एक निःशुल्क पाठ है। यह 4 में से 2वाँ पाठ है। आप नीचे पूरा पाठ निःशुल्क पढ़ सकते हैं—फिर अंतर्निहित कोड संपादक और 24/7 एआई ट्यूटर के साथ ब्राउज़र में इसका व्यावहारिक अभ्यास कर सकते हैं। यह NestJS एंटरप्राइज़ बैकएंड API सीखने के मार्ग का हिस्सा है और आपकी प्रगति वेब तथा CoddyKit ऐप पर सिंक होती रहती है। NestJS एंटरप्राइज़ बैकएंड API पाठ्यक्रम में कुल 4 पाठ शामिल हैं।
प्लगइन जोड़ने की समस्या
एक प्लगइन आर्किटेक्चर में संकलन के समय यह पता नहीं होता कि कौन-से हैंडलर, रणनीतियाँ या अडैप्टर मौजूद होंगे। षट्भुजीय कोर पोर्ट परिभाषित करता है; प्लगइन अडैप्टर उपलब्ध कराते हैं। चुनौती यह है: प्रत्येक अडैप्टर को हाथ से पंजीकृत किए बिना फ़्रेमवर्क उन्हें कैसे ढूँढ़ेगा और जोड़ेगा?
- किसी मॉड्यूल में हार्ड-कोड की गई सारणियाँ नाज़ुक होती हैं — हर नए प्लगइन के लिए कोर कोड में बदलाव करना पड़ता है।
- आप चाहते हैं कि प्रदाता डेकोरेटर के माध्यम से अपनी भूमिका स्वयं घोषित करें और फिर रनटाइम पर खोजे जाएँ।
NestJS में इसके लिए @nestjs/core की DiscoveryService और MetadataScanner उपलब्ध हैं। इनके माध्यम से सक्रिय DI कंटेनर को स्कैन करके मेटाडेटा पर प्रतिक्रिया दी जा सकती है।
डेकोरेटर से प्रदाताओं को चिह्नित करना
यह तरीका एक ऐसे कस्टम डेकोरेटर से शुरू होता है जो किसी क्लास पर मेटाडेटा अंकित करता है। हम SetMetadata (या Reflector.createDecorator) का उपयोग करते हैं, ताकि स्कैनर बाद में उसे फ़िल्टर कर सके।
यहाँ @Plugin() डेकोरेटर किसी क्लास को खोजे जा सकने वाले प्लगइन के रूप में चिह्नित करता है और एक name रखता है, जिसके आधार पर रजिस्ट्री कुंजी बना सकती है।
import { SetMetadata } from '@nestjs/common';
export const PLUGIN_KEY = 'app:plugin';
export interface PluginMeta {
name: string;
}
export const Plugin = (meta: PluginMeta): ClassDecorator =>
SetMetadata(PLUGIN_KEY, meta);
// A plugin author writes:
@Plugin({ name: 'csv-exporter' })
export class CsvExporter {
export(rows: unknown[]): string {
return rows.map((r) => JSON.stringify(r)).join('\n');
}
}DiscoveryService आपको क्या देता है
DiscoveryService को DiscoveryModule निर्यात करता है। इसे इंजेक्ट करने पर आपको दो महत्वपूर्ण विधियाँ मिलती हैं:
getProviders()— एप्लिकेशन कंटेनर में मौजूद प्रत्येक प्रदाता इंस्टेंस रैपर।getControllers()— प्रत्येक कंट्रोलर रैपर।
हर आइटम एक InstanceWrapper होता है, जिसमें .instance (सक्रिय ऑब्जेक्ट), .metatype (क्लास) और .name होते हैं। Reflector की सहायता से metatype से मेटाडेटा पढ़कर इन रैपरों को फ़िल्टर किया जाता है।
import { Module } from '@nestjs/common';
import { DiscoveryModule } from '@nestjs/core';
import { PluginRegistry } from './plugin.registry';
@Module({
imports: [DiscoveryModule], // exposes DiscoveryService + MetadataScanner
providers: [PluginRegistry],
exports: [PluginRegistry],
})
export class PluginCoreModule {}बूटस्ट्रैप पर प्रदाताओं को स्कैन करना
कंटेनर के पूरी तरह बन जाने के बाद खोज चलाएँ। OnModuleInit लागू करें (या यदि हर मॉड्यूल के तैयार होने की आवश्यकता हो तो OnApplicationBootstrap)। उन रैपरों को फ़िल्टर करें जिनके metatype में आपका PLUGIN_KEY मेटाडेटा मौजूद है।
null रैपरों से सावधान रहें: कुछ प्रविष्टियों (वैल्यू प्रदाताओं और अनुरोध-स्कोप वाले प्लेसहोल्डरों) में metatype या instance नहीं होता।
import { Injectable, OnModuleInit } from '@nestjs/common';
import { DiscoveryService, Reflector } from '@nestjs/core';
import { PLUGIN_KEY, PluginMeta } from './plugin.decorator';
@Injectable()
export class PluginRegistry implements OnModuleInit {
private readonly plugins = new Map<string, object>();
constructor(
private readonly discovery: DiscoveryService,
private readonly reflector: Reflector,
) {}
onModuleInit(): void {
for (const wrapper of this.discovery.getProviders()) {
const { instance, metatype } = wrapper;
if (!instance || !metatype) continue;
const meta = this.reflector.get<PluginMeta>(PLUGIN_KEY, metatype);
if (!meta) continue;
this.plugins.set(meta.name, instance);
}
}
get(name: string): object | undefined {
return this.plugins.get(name);
}
}विधि-स्तरीय हुक के लिए MetadataScanner
कभी-कभी प्लगइन बिंदु क्लास नहीं, बल्कि कोई विधि होता है — जैसे अलग-अलग विधियों पर @EventHandler('order.created')। MetadataScanner किसी इंस्टेंस के प्रोटोटाइप की हर विधि पर चलता है, ताकि आप प्रत्येक विधि का मेटाडेटा पढ़ सकें।
getAllMethodNames(prototype) (आधुनिक API) का उपयोग करें और Reflector से प्रत्येक हैंडलर की जाँच करें।
import { Injectable, OnModuleInit } from '@nestjs/common';
import { DiscoveryService, MetadataScanner, Reflector } from '@nestjs/core';
export const EVENT_KEY = 'app:event';
@Injectable()
export class EventBinder implements OnModuleInit {
constructor(
private readonly discovery: DiscoveryService,
private readonly scanner: MetadataScanner,
private readonly reflector: Reflector,
) {}
onModuleInit(): void {
for (const w of this.discovery.getProviders()) {
if (!w.instance || !w.metatype) continue;
const proto = Object.getPrototypeOf(w.instance);
for (const method of this.scanner.getAllMethodNames(proto)) {
const event = this.reflector.get<string>(EVENT_KEY, proto[method]);
if (event) this.bind(event, w.instance, method);
}
}
}
private bind(event: string, target: object, method: string): void {
// register target[method] as a listener for `event`
}
}विधि-स्तरीय डेकोरेटर
बाइंडर के साथ एक विधि डेकोरेटर का उपयोग करें। ध्यान दें कि यह MethodDecorator है — SetMetadata मान को विधि के descriptor.value से जोड़ता है, जिसे reflector.get(EVENT_KEY, proto[method]) ठीक उसी तरह पढ़ता है।
इससे जोड़ना घोषणात्मक बना रहता है: प्लगइन लेखक एक एनोटेशन जोड़ता है और कोर उसे बाँध देता है — मैन्युअल emitter.on(...) कॉल की आवश्यकता नहीं होती।
import { SetMetadata } from '@nestjs/common';
import { EVENT_KEY } from './event.binder';
export const OnEvent = (event: string): MethodDecorator =>
SetMetadata(EVENT_KEY, event);
@Injectable()
export class InventoryPlugin {
@OnEvent('order.created')
reserveStock(payload: { orderId: string }): void {
// adjust stock for payload.orderId
}
@OnEvent('order.cancelled')
releaseStock(payload: { orderId: string }): void {
// restore stock
}
}साधारण TypeScript में खोज का मॉडल बनाना
NestJS को हटा दें तो मूल विचार सरल है: एक रजिस्ट्री सूची से खोजे गए इंस्टेंस को किसी कुंजी से मैप करती है, फिर कुंजी के आधार पर कॉल भेजती है। यह स्वतंत्र मॉडल उन रजिस्ट्री अर्थविधियों को दर्शाता है जिन्हें आप DiscoveryService से जोड़ेंगे।
वास्तविक रजिस्ट्री में भी यही Map-आधारित खोज काम करती है — केवल इंस्टेंस का स्रोत अलग होता है।
interface Exporter {
readonly name: string;
export(rows: object[]): string;
}
class CsvExporter implements Exporter {
name = 'csv';
export(rows: object[]): string {
return rows.map((r) => Object.values(r).join(',')).join('\n');
}
}
class JsonExporter implements Exporter {
name = 'json';
export(rows: object[]): string {
return JSON.stringify(rows);
}
}
class Registry {
private map = new Map<string, Exporter>();
register(...plugins: Exporter[]): void {
for (const p of plugins) this.map.set(p.name, p);
}
run(name: string, rows: object[]): string {
const p = this.map.get(name);
if (!p) throw new Error('Unknown exporter: ' + name);
return p.export(rows);
}
}
const reg = new Registry();
reg.register(new CsvExporter(), new JsonExporter());
const data = [{ id: 1, sku: 'A' }, { id: 2, sku: 'B' }];
console.log(reg.run('csv', data));
console.log(reg.run('json', data));खोज का समय और जीवनचक्र
समय महत्वपूर्ण है। DI कंटेनर केवल कुछ निश्चित जीवनचक्र चरणों में पूर्ण होता है:
onModuleInit— प्रत्येक मॉड्यूल के लिए उसके प्रदाताओं के हल हो जाने के बाद चलता है। यदि सभी प्लगइन एक ही मॉड्यूल में हों तो पर्याप्त है।onApplicationBootstrap— सभी मॉड्यूल के प्रारंभ होने के बाद एक बार चलता है। मॉड्यूलों के बीच प्लगइन स्कैनिंग के लिए सबसे सुरक्षित है।
बहुत जल्दी स्कैन करने पर प्रदाताओं की सूची खाली या अधूरी मिलती है। जब प्लगइन बाद में लोड किए गए सुविधा मॉड्यूलों में आ सकते हों, तो onApplicationBootstrap को प्राथमिकता दें।
import { Injectable, OnApplicationBootstrap } from '@nestjs/common';
import { DiscoveryService, Reflector } from '@nestjs/core';
import { PLUGIN_KEY, PluginMeta } from './plugin.decorator';
@Injectable()
export class PluginRegistry implements OnApplicationBootstrap {
private readonly plugins = new Map<string, object>();
constructor(
private readonly discovery: DiscoveryService,
private readonly reflector: Reflector,
) {}
onApplicationBootstrap(): void {
const found = this.discovery
.getProviders()
.filter((w) => w.instance && w.metatype)
.map((w) => ({
meta: this.reflector.get<PluginMeta>(PLUGIN_KEY, w.metatype!),
instance: w.instance,
}))
.filter((x) => x.meta);
for (const { meta, instance } of found) {
this.plugins.set(meta!.name, instance);
}
}
}स्कोप की समस्याएँ: REQUEST और TRANSIENT
खोज में सिंगलटन आसानी से दिखाई देते हैं। डिफ़ॉल्ट के अलावा अन्य स्कोप से सावधान रहें:
Scope.REQUEST/Scope.TRANSIENTप्रदाताओं में बूटस्ट्रैप के समयwrapper.instance === nullहो सकता है — कैश करने के लिए कोई एकल इंस्टेंस नहीं होता।- खोजे गए अनुरोध-स्कोप वाले इंस्टेंस को कैश करने पर वह पुराना हो जाएगा और प्रत्येक अनुरोध की स्थिति लीक हो सकती है।
सामान्य नियम: प्लगइन को सिंगलटन-स्कोप वाला रखें। यदि किसी प्लगइन को वास्तव में अनुरोध डेटा चाहिए, तो क्लास को खोजें और इंस्टेंस को कैश करने के बजाय प्रत्येक अनुरोध पर ModuleRef.resolve() से नया इंस्टेंस प्राप्त करें।
import { Injectable } from '@nestjs/common';
import { ModuleRef } from '@nestjs/core';
@Injectable()
export class ScopedPluginInvoker {
constructor(private readonly moduleRef: ModuleRef) {}
// metatype was discovered earlier; resolve fresh per request
async invoke<T>(metatype: new (...a: any[]) => T): Promise<T> {
return this.moduleRef.resolve(metatype, undefined, { strict: false });
}
}रजिस्ट्री का सत्यापन और सुरक्षा
ऐसी प्लगइन प्रणाली जो डुप्लिकेट या अनुपस्थित अनुबंधों को चुपचाप अनदेखा कर दे, डीबग करना बेहद कठिन बना देती है। खोज के दौरान सुरक्षा-जाँच जोड़ें:
- डुप्लिकेट नाम — ओवरराइट करने के बजाय त्रुटि उत्पन्न करें, ताकि दो प्लगइन एक ही कुंजी पर टकरा न सकें।
- अनुबंध जाँच — भरोसा करने से पहले सत्यापित करें कि इंस्टेंस अपेक्षित विधि-रूप लागू करता है।
बूटस्ट्रैप पर तुरंत विफल होने से रनटाइम प्लगइन बग एक स्पष्ट स्टार्टअप त्रुटि में बदल जाता है।
private register(name: string, instance: object): void {
if (this.plugins.has(name)) {
throw new Error(`Duplicate plugin name: ${name}`);
}
if (typeof (instance as { export?: unknown }).export !== 'function') {
throw new Error(`Plugin ${name} missing export()`);
}
this.plugins.set(name, instance);
}यह षट्भुजीय डिज़ाइन के अनुकूल क्यों है
खोज-आधारित पंजीकरण पोर्ट और अडैप्टर का रनटाइम जोड़ने वाला तंत्र है:
- कोर एक पोर्ट (इंटरफ़ेस) और क्षमता के आधार पर कुंजी वाली रजिस्ट्री परिभाषित करता है।
- हर अडैप्टर/प्लगइन डेकोरेटर से स्वयं को घोषित करता है — वह कोर के अनुबंध पर निर्भर करता है, उलटे नहीं।
- किसी क्षमता को जोड़ने के लिए एक नया एनोटेट किया हुआ प्रदाता जोड़ना पर्याप्त है; कोर के जोड़ने वाले कोड में कोई बदलाव नहीं करना पड़ता।
इससे निर्भरता की दिशा उलट जाती है (निर्भरता उलटने का सिद्धांत) और कोर संशोधन के लिए बंद, लेकिन विस्तार के लिए खुला रहता है — यही प्लगइन आर्किटेक्चर का मूल है।
त्वरित जाँच
आप एक ऐसी प्लगइन रजिस्ट्री बनाते हैं जो onApplicationBootstrap के दौरान खोजे गए प्रत्येक प्रदाता के .instance को Map में कैश करती है। एक प्लगइन Scope.REQUEST के साथ घोषित है। क्या समस्या होगी और सही समाधान क्या है?
पुनरावलोकन
आपने NestJS की खोज संबंधी मूल सुविधाओं पर आधारित एक रनटाइम प्लगइन प्रणाली बनाई:
- प्लगइन को चिह्नित करें मेटाडेटा डेकोरेटर से (
SetMetadata+PLUGIN_KEY), पूरे प्लगइन के लिए क्लास-स्तर पर और हैंडलर के लिए विधि-स्तर पर। - कंटेनर को स्कैन करें
DiscoveryService.getProviders()से औरReflectorके माध्यम से रैपरों को फ़िल्टर करें; प्रत्येक विधि के हुक के लिएMetadataScanner.getAllMethodNames()का उपयोग करें। - समय सही रखें मॉड्यूलों के बीच सुरक्षा के लिए
onApplicationBootstrapके साथ, औरinstance/metatypeसे रहित रैपरों को छोड़ दें। - सुरक्षा-जाँच करें डुप्लिकेट कुंजियों और अनुबंध उल्लंघनों से; प्लगइन को सिंगलटन-स्कोप वाला रखें और केवल तब
ModuleRefके माध्यम से हल करें जब अनुरोध स्कोप वास्तव में आवश्यक हो।
लाभ: ऐसा षट्भुजीय कोर जो संशोधन के लिए बंद, लेकिन नए एनोटेट किए गए अडैप्टर के लिए खुला हो — प्रत्येक प्लगइन के लिए कोर में शून्य बदलाव।
एआई शिक्षक के साथ TypeScript सीखें — निःशुल्क
अपने ब्राउज़र में वास्तविक कोड लिखें और चलाएँ, चौबीसों घंटे एआई शिक्षक से तुरंत सहायता पाएँ, और वेब या ऐप पर वहीं से शुरू करें जहाँ आपने छोड़ा था।
- पाठ्यक्रम
- 20
- पाठ
- 76
अक्सर पूछे जाने वाले प्रश्न
क्या “DiscoveryService के साथ गतिशील प्रदाता पंजीकरण” पाठ निःशुल्क है?
हाँ—“DiscoveryService के साथ गतिशील प्रदाता पंजीकरण” का पूरा पाठ यहाँ वेब पर निःशुल्क पढ़ा जा सकता है। इंटरैक्टिव अभ्यास (अंतर्निहित कोड संपादक और 24/7 एआई ट्यूटर) करने और NestJS एंटरप्राइज़ बैकएंड API पाठ्यक्रम का बाकी हिस्सा अनलॉक करने के लिए CoddyKit PRO लें। NestJS एंटरप्राइज़ बैकएंड API पाठ्यक्रम में कुल 4 पाठ शामिल हैं।
“DiscoveryService के साथ गतिशील प्रदाता पंजीकरण” में मैं क्या सीखूँगा?
प्लगइन प्रणालियों के लिए DiscoveryService और MetadataScanner से रनटाइम पर प्रदाताओं को स्कैन और जोड़िए। आप ब्राउज़र में सीधे चलाए जाने वाले व्यावहारिक कोड के साथ NestJS एंटरप्राइज़ बैकएंड API का अभ्यास करते हैं, और पाठ पूरा करते समय 24/7 एआई ट्यूटर आपके प्रश्नों के उत्तर देता है।
क्या NestJS एंटरप्राइज़ बैकएंड API शुरू करने के लिए मुझे किसी अनुभव की आवश्यकता है?
पहले के अनुभव की आवश्यकता नहीं है। CoddyKit पर NestJS एंटरप्राइज़ बैकएंड API शुरुआती से लेकर उन्नत शिक्षार्थियों तक सभी के लिए व्यवस्थित किया गया है, इसलिए आप यहीं से या शुरुआत से सीखना शुरू कर सकते हैं और अपनी गति से आगे बढ़ सकते हैं। यह 4 में से 2वाँ पाठ है।
“DiscoveryService के साथ गतिशील प्रदाता पंजीकरण” पाठ पूरा करने में कितना समय लगता है?
CoddyKit का अधिकांश पाठ लगभग 5–10 मिनट में पूरा हो जाता है। हर पाठ छोटा और संवादात्मक है, इसलिए आप लगातार प्रगति करते हैं और वेब या ऐप पर वहीं से सीखना जारी रख सकते हैं जहाँ आपने छोड़ा था।
क्या मैं इस NestJS एंटरप्राइज़ बैकएंड API पाठ में कोड लिख और चला सकता हूँ?
हाँ। हर NestJS एंटरप्राइज़ बैकएंड API पाठ में एक अंतर्निर्मित कोड संपादक शामिल है, जिससे आप सीधे अपने ब्राउज़र में वास्तविक कोड लिख और चला सकते हैं और तुरंत एआई प्रतिक्रिया पा सकते हैं—स्थानीय सेटअप की आवश्यकता नहीं है।
इस पाठ्यक्रम के सभी पाठ
- डोमेन अलगाव के लिए पोर्ट और अडैप्टर
- DiscoveryService के साथ गतिशील प्रदाता पंजीकरण
- विलंबित-लोड मॉड्यूल और फ़ीचर टॉगल
- मॉड्यूल संदर्भ API के साथ विस्तार बिंदु