Kodokon kodokon.com

API को आर्किटेक्ट करना: रूट, कंट्रोलर, सर्विस

अपने API को एकल-ज़िम्मेदारी वाली परतों में व्यवस्थित करें और उन्हें हल्के इंजेक्शन से जोड़कर टेस्ट करने योग्य बनाएँ।

10 मिनट · 3 प्रश्न

इस पाठ को Kodokon में खोलें

एक ही फ़ाइल जो रूटिंग, बिज़नेस लॉजिक और डेटा एक्सेस को आपस में मिला देती है, वह हमेशा महँगी साबित होती है: आप सर्वर चालू किए बिना बिज़नेस नियमों को टेस्ट नहीं कर सकते, और आप सब कुछ दोबारा लिखे बिना इंफ़्रास्ट्रक्चर नहीं बदल सकते। तीन परतों में बाँटने से दोनों समस्याएँ हल हो जाती हैं। रूट URL घोषित करते हैं और काम सौंप देते हैं। कंट्रोलर HTTP को बिज़नेस कॉल में बदलते हैं: अनुरोध पढ़ना, स्टेटस कोड चुनना, प्रतिक्रिया को सीरियलाइज़ करना। सर्विस बिज़नेस नियमों को रखती हैं और Express के बारे में कुछ भी नहीं जानतीं। निर्भरता का नियम कठोर है: हर परत केवल अपने से नीचे वाली परत को जानती है, कभी उल्टा नहीं।

JAVASCRIPT
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) नहीं।

JAVASCRIPT
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);
      }
    },
  };
}
शुद्ध HTTP अनुवाद: स्टेटस कोड, सीरियलाइज़ेशन, कार्य-सौंपना।

सर्विस निर्णयों को एक जगह केंद्रित करती है: ईमेल की विशिष्टता, निर्माण के नियम। यह बिज़नेस त्रुटियाँ फेंकती है - अधिक से अधिक एक status फ़ील्ड के साथ समृद्ध - जबकि Express के बारे में कुछ नहीं जानती। यह भी अपनी निर्भरता, यानी डेटा रिपॉज़िटरी, को एक आर्गुमेंट के रूप में प्राप्त करती है। यही हल्का इंजेक्शन है: स्टार्टअप पर एक बार कॉल किए गए सादे फ़ैक्ट्री फ़ंक्शन भारी इंजेक्शन कंटेनरों की जगह लेते हैं, और हर परत को टेस्ट में बदलने योग्य बना देते हैं।

JAVASCRIPT
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);
    },
  };
}
बिज़नेस लॉजिक यहाँ रहता है, बिना किसी req, res या SQL के।

बचता है कंपोज़िशन रूट: वह एकमात्र जगह जहाँ फ़ैक्ट्रियाँ आपस में जुड़ती हैं। createApp इंफ़्रास्ट्रक्चर की निर्भरताएँ प्राप्त करता है (यहाँ डेटाबेस, जिसे अगले पाठ में देखा गया है) और एक पूर्ण Express एप्लिकेशन लौटाता है जो अभी सुन नहीं रहा। निर्माण को सुनने से अलग करना मामूली लगता है; यही वह चीज़ है जो टेस्ट को एक अस्थायी पोर्ट पर API माउंट करने देगी जबकि प्रोडक्शन सर्वर तीन लाइनों में सिमट जाता है: डेटाबेस बनाओ, ऐप बनाओ, सुनो।

JAVASCRIPT
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;
}
app.js: एप्लिकेशन का एकमात्र हिस्सा जो सबको जानता है।

ज्ञान जांच

सुनिश्चित करें कि आपको इस पाठ के मुख्य बिंदु याद हैं।

  1. इस परतबंदी में, यह नियम "एक ईमेल केवल एक बार उपयोग किया जा सकता है" कहाँ रहना चाहिए?
    • रूट में, जितना संभव हो URL के करीब
    • कंट्रोलर में, जिसकी req.body तक पहुँच है
    • सर्विस में, जो बिज़नेस नियमों को रखती है
    • एक ग्लोबल Express मिडलवेयर में
  2. निर्भरताओं को हर मॉड्यूल में सीधे इम्पोर्ट करने के बजाय फ़ैक्ट्री आर्गुमेंट के रूप में पास करने का मुख्य लाभ क्या है?
    • फ़ैक्ट्रियाँ मॉड्यूल लोडिंग को तेज़ करती हैं
    • आप उसका उपयोग करने वाले मॉड्यूल को छुए बिना किसी निर्भरता (इन-मेमोरी डेटाबेस, नकली रिपॉज़िटरी) को बदल सकते हैं
    • Express अपने मिडलवेयर के लिए इस शैली की माँग करता है
    • यह अलग फ़ाइलें लिखने से बचाता है
  3. createApp स्वयं नेटवर्क पर सुनना क्यों शुरू नहीं करता?
    • क्योंकि Express किसी फ़ंक्शन के अंदर listen कॉल करने की मनाही करता है
    • ताकि टेस्ट एक निश्चित पोर्ट खोले बिना एप्लिकेशन को इंस्टैंशिएट कर सकें, और सर्वर का प्रवेश-बिंदु मामूली बना रहे
    • क्योंकि listen अतुल्यकालिक है और फ़ैक्ट्री को अवरुद्ध कर देगा
    • स्टार्टअप पर मेमोरी उपयोग कम करने के लिए