अपने API को एकल-ज़िम्मेदारी वाली परतों में व्यवस्थित करें और उन्हें हल्के इंजेक्शन से जोड़कर टेस्ट करने योग्य बनाएँ।
इस पाठ को Kodokon में खोलेंएक ही फ़ाइल जो रूटिंग, बिज़नेस लॉजिक और डेटा एक्सेस को आपस में मिला देती है, वह हमेशा महँगी साबित होती है: आप सर्वर चालू किए बिना बिज़नेस नियमों को टेस्ट नहीं कर सकते, और आप सब कुछ दोबारा लिखे बिना इंफ़्रास्ट्रक्चर नहीं बदल सकते। तीन परतों में बाँटने से दोनों समस्याएँ हल हो जाती हैं। रूट URL घोषित करते हैं और काम सौंप देते हैं। कंट्रोलर HTTP को बिज़नेस कॉल में बदलते हैं: अनुरोध पढ़ना, स्टेटस कोड चुनना, प्रतिक्रिया को सीरियलाइज़ करना। सर्विस बिज़नेस नियमों को रखती हैं और Express के बारे में कुछ भी नहीं जानतीं। निर्भरता का नियम कठोर है: हर परत केवल अपने से नीचे वाली परत को जानती है, कभी उल्टा नहीं।
import { Router } from 'express';
export function createUserRouter(controller) {
const router = Router();
router.get('/', controller.list);
router.post('/', controller.create);
return router;
}कंट्रोलर एक फ़ैक्ट्री है जो सर्विस को एक आर्गुमेंट के रूप में प्राप्त करती है। इसमें कोई बिज़नेस नियम नहीं होता: इसका काम केवल req से डेटा निकालने, सर्विस को कॉल करने और परिणाम को HTTP प्रतिक्रिया में बदलने तक सीमित है। कोई भी त्रुटि next को भेजी जाती है ताकि उसे केंद्रीय एरर मिडलवेयर संभाले - कभी भी कंट्रोलरों में बिखरा हुआ res.status(500) नहीं।
export function createUserController(service) {
return {
async list(req, res, next) {
try {
const users = await service.listUsers();
res.json(users);
} catch (err) {
next(err);
}
},
async create(req, res, next) {
try {
const user = await service.createUser(req.body);
res.status(201).json(user);
} catch (err) {
next(err);
}
},
};
}सर्विस निर्णयों को एक जगह केंद्रित करती है: ईमेल की विशिष्टता, निर्माण के नियम। यह बिज़नेस त्रुटियाँ फेंकती है - अधिक से अधिक एक status फ़ील्ड के साथ समृद्ध - जबकि Express के बारे में कुछ नहीं जानती। यह भी अपनी निर्भरता, यानी डेटा रिपॉज़िटरी, को एक आर्गुमेंट के रूप में प्राप्त करती है। यही हल्का इंजेक्शन है: स्टार्टअप पर एक बार कॉल किए गए सादे फ़ैक्ट्री फ़ंक्शन भारी इंजेक्शन कंटेनरों की जगह लेते हैं, और हर परत को टेस्ट में बदलने योग्य बना देते हैं।
export function createUserService(repository) {
return {
listUsers() {
return repository.findAll();
},
createUser(input) {
const found = repository.findByEmail(input.email);
if (found) {
const err = new Error('Email already used');
err.status = 409;
throw err;
}
return repository.insert(input);
},
};
}बचता है कंपोज़िशन रूट: वह एकमात्र जगह जहाँ फ़ैक्ट्रियाँ आपस में जुड़ती हैं। createApp इंफ़्रास्ट्रक्चर की निर्भरताएँ प्राप्त करता है (यहाँ डेटाबेस, जिसे अगले पाठ में देखा गया है) और एक पूर्ण Express एप्लिकेशन लौटाता है जो अभी सुन नहीं रहा। निर्माण को सुनने से अलग करना मामूली लगता है; यही वह चीज़ है जो टेस्ट को एक अस्थायी पोर्ट पर API माउंट करने देगी जबकि प्रोडक्शन सर्वर तीन लाइनों में सिमट जाता है: डेटाबेस बनाओ, ऐप बनाओ, सुनो।
import express from 'express';
import { createUserRepository } from './db/users.js';
import { createUserService } from './services/users.js';
import {
createUserController,
} from './controllers/users.js';
import { createUserRouter } from './routes/users.js';
export function createApp({ db }) {
const app = express();
app.use(express.json());
const repository = createUserRepository(db);
const service = createUserService(repository);
const controller = createUserController(service);
app.use('/api/users', createUserRouter(controller));
return app;
}createApp स्वयं नेटवर्क पर सुनना क्यों शुरू नहीं करता?