تزریق وابستگی در تایپ اسکریپت – به زبان ساده
تزریق وابستگی یک ابزار بسیار قدرتمند در اپلیکیشنهایی با هر اندازه محسوب میشود. هدف این مقاله توضیح روش اجرای تزریق وابستگی یا اهمیت استفاده از آن نیست. در این راهنما تلاش کردهایم تا سادهترین و سرراستترین روشهای تزریق وابستگی در تایپ اسکریپت را معرفی کنیم.
ساختار پروژه
ریشه پروژه ما یک فایل index.ts است که در پوشه src قرار دارد. این نقطه ورودی اصلی کد ما محسوب میشود. ما میخواهیم ساختار پروژهمان از نظر بصری نماینده بخشهای مختلفی باشد که در پروژه وجود دارند. این وضعیت از طریق استفاده از سلسلهمراتب پوشه به دست میآید. یک بخش میتواند هر چیزی باشد به شرط این که معنی داشته باشد. برای نمونه میتوانیم بخشی برای سرویسهایی داشته باشیم که از ماژول دیگر فرا میخوانیم. یا این که میتوانیم بخشی برای اینترفیسها داشته باشیم که در سراسر اپلیکیشن استفاده میشوند. این یک ایده کاملاً عمومی است، اما درک شیوه تجزیه اپلیکیشن به بخشهای مختلف میتواند موجب شود که اپلیکیشنهای پیچیده را بهتر و آسانتر درک کنیم.
هر یک از این بخشها به یک ماژول اکسپورت میشوند. این ماژول همه ارجاعها و وهلههای آیتمها را در این بخش که اپلیکیشن نیاز دارد اکسپورت خواهد کرد. بدین ترتیب ارجاع به آیتمها بسیار تمیز میماند و مبنایی برای تزریق وابستگی به دست میآید.
اکنون به بحث اصلی این مقاله میرسیم: در صورتی که یک آیتم در بخشی از اپلیکیشن به آیتمی در بخش دیگر نیاز داشته باشد چه اتفاقی میافتد؟ فرض کنید کلاس B به کلاس A وابسته باشد. یک روش کمی متفاوت برای طرح سؤال این است که کلاس A چطور به کلاس B تزریق میشود؟
اگر به خاطر داشته باشید ما همه آیتمهای بخش را به صورت یک ماژول وهلهسازی و اکسپورت کردیم. بنابراین هنگامی که وهلهای از کلاس B بسازیم، وهلهای از کلاس A را از ماژول متناظر ایمپورت کرده و در کلاس B تزریق میکنیم.
مثال عملی
اگر با خواندن توضیح قبلی نتوانستید فرایند کار را به خوبی درک کنید جای نگرانی نیست، چون در این بخش یک مثال عملی برای درک بهتر موضوع آشنا خواهیم شد.
این مثال یک سرور ساده ExpressJS است که در آن کاربران با استفاده از یک id دریافت (GET) میشوند و کاربران جدید نیز POST میشوند. ساختار پروژه ما به صورت زیر است:
در این بخش دلایلمان را برای ایجاد بخش endpoints و services توضیح میدهیم:
Endpoints
این بخش همه منطق تجاری برای هر نقطه انتهایی را نگهداری میکند. این بخش شامل هیچ کدی در مورد اتصال به پایگاه داده نیست و حتی به نوع پایگاه داده که استفاده میشود نیز اهمیتی نمیدهد. ما باید بتوانیم سیستم پایگاه داده را بدون نیاز به ویرایش این بخش به طور کامل عوض کنیم.
Services
این بخش شامل همه اطلاعات اتصال به پایگاه داده است و مسئول افزودن کاربران جدید و دریافت کاربران موجود است. این بخش سرویسی نیز برای ارسال ایمیلهای خوشامدگویی به کاربران جدید نگهداری میکند.
کار خود را با تنظیم فایل index.ts آغاز میکنیم:
1import * as express from "express";
2
3const app = express();
4
5app.get(
6 "/customer/:id",
7 async (req: express.Request, res: express.Response) => {}
8);
9
10app.post(
11 "/customer",
12 async (req: express.Request, res: express.Response) => {}
13);
14
15app.listen(3000, () => {
16 console.log("App listening at localhost:3000");
17});
کد فوق یک سرور کاملاً ابتدایی ExpressJS روی پورت 3000 ایجاد میکند. این سرور یک مسیر GET برای دریافت مشتریان بر اساس id و یک مسیر POST برای ایجاد یک مشتری جدید دارد.
Services
در ادامه فایلی در پوشه Services به نام database.ts میسازیم و سرویس پایگاه داده را در آن قرار میدهیم:
1export class Database {
2 constructor() {
3 this.init();
4 }
5
6 /**
7 * Put all of your database initializtion logic in this function
8 */
9 private init() {}
10
11 public async getCustomerById(id: string) {}
12
13 public async postNewCustomer(customer: any) {}
14}
این فایل هنوز کامل نشده است. با پر کردن این توابع میتوانیم یک اتصال با هر نوع پایگاه داده که میخواهیم استفاده کنیم برقرار سازیم. فعلاً آنها را خالی باقی میگذاریم.
در ادامه همین کار را در مورد یک کلاس EmailSender انجام میدهیم. یک فایل به نام emailSender.ts در پوشه services ایجاد میکنیم:
1export class EmailSender {
2 constructor() {
3 this.init();
4 }
5
6 /**
7 * Place all initialization logic in this function
8 */
9 private init() {}
10
11 public async sendWelcomeEmail(customer: any) {}
12}
در این مورد نیز تابعها خالی هستند. بدین ترتیب میتوانیم متدهای مناسبی را در نقاط انتخابی و با پیادهسازی متناظر کارکردها فراخوانی کنیم. این تابعها را میتوانید با هر سرویس ایمیل که ترجیح میدهید پر کنید.
اکنون باید این سرویسها را درون یک ماژول قرار دهیم. یک فایل به نام index.ts در پوشه services ایجاد میکنیم. این فایلی حیاتی برای ماژول ما محسوب میشود:
1import { Database } from "./database";
2import { EmailSender } from "./emailSender";
3
4// Export class references
5export { Database } from "./database";
6export { EmailSender } from "./emailSender";
7
8// Export instantiated services
9export const db = new Database();
10export const emailSender = new EmailSender();
این فایل دو کار انجام میدهد:
ارجاعهای کلاس را از این فایل اکسپورت میکند. این بدان معنی است که در ادامه به کلاس پایگاه داده ارجاع میدهیم و میتوانیم آن را از پوشه services بدون نیاز به ارجاع به فایل database.ts ایمپورت کنیم.
همچنین نسخههای وهلهسازی شده از این سرویسها را اکسپورت میکند. این const-ها آن چیزی هستند که در نقاط انتخابی در گام بعدی تزریق خواهیم کرد.
نقاط انتهایی
در این بخش فایلی به نام getCustomer.ts در پوشه endpoints ایجاد میکنیم:
1import { Database } from "../services";
2
3export function makeGetCustomer(db: Database) {
4 return async function getCustomer(id: string) {
5 try {
6 const customer = await db.getCustomerById(id);
7 return customer;
8 } catch (e) {
9 console.log(`Failed to retrieve customer from database, id: ${id}`);
10 console.log(e);
11 return;
12 }
13 };
14}
یک تابع به نام makeGetCustomer اکسپورت میکنیم و یک تابع async به نام getCustomer بازگشت میدهیم. این تابع getCustomer چیزی است که نقطه انتهایی ExpressJS مورد استفاده قرار خواهد داد. منطق تجاری بسیار سرراست است. تلاش میکنیم مشتری را از پایگاه داده بگیریم، در صورت موفقیت مشتری را بازگشت میدهیم و در غیر این صورت خطا را لاگ میکنیم. توجه کنید که ارجاع کلاس پایگاه داده را مستقیماً از پوشه services ایمپورت میکنیم.
اکنون فایلی به نام postCustomer.ts در پوشه endpoints ایجاد میکنیم:
1import { Database, EmailSender } from "../services";
2
3export function makePostCustomer(db: Database, emailSender: EmailSender) {
4 return async function postCustomer(customer: any) {
5 try {
6 // Post the new customer to the database
7 await db.postNewCustomer(customer);
8
9 // Send the new customer a welcome email
10 await emailSender.sendWelcomeEmail(customer);
11
12 return;
13 } catch (e) {
14 console.log(`There was a problem with this customer`, customer);
15 console.log(e);
16 }
17 };
18}
اکنون که منطق تجاری را برای دو نقطه انتهایی نوشتیم، فایلی به نام index.ts در پوشه endpoints میسازیم.
1import { db, emailSender } from "../services";
2import { makeGetCustomer } from "./getCustomer";
3import { makePostCustomer } from "./postCustomer";
4
5// Export the endpoint functions
6export const getCustomer = makeGetCustomer(db);
7export const postCustomer = makePostCustomer(db, emailSender);
این فایل بسیار شبیه به فایل اندیس در services است به جز این که هیچ ارجاع کلاسی برای اکسپورت ندارد.
زمانی که const-های db و emailSender را ایمپورت میکنیم، آنها را در ماژول services وهلهسازی و اکسپورت میکنیم. هر دو نقطه انتهایی getCustomer و postCustomer از وهله یکسانی از پایگاه داده استفاده میکنند چون طرز کار تزریق وابستگی مبتنی بر ماژول این گونه است.
اکنون فایل index.ts را در ریشه پروژه بهروزرسانی میکنیم تا به صورت زیر درآید:
1import * as express from "express";
2import { getCustomer, postCustomer } from "./endpoints";
3
4const app = express();
5
6app.get(
7 "/customer/:id",
8 async (req: express.Request, res: express.Response) => {
9 const customer = await getCustomer(req.param("id"));
10
11 res
12 .status(202)
13 .json(customer)
14 .end();
15 }
16);
17
18app.post("/customer", async (req: express.Request, res: express.Response) => {
19 await postCustomer(req.body);
20
21 res.status(202).end();
22});
23
24app.listen(3000, () => {
25 console.log("App listening at localhost:3000");
26});
سخن پایانی
الگوی تزریق وابستگی که در این راهنما مطرح کردیم، بسیار قدرتمند است، زیرا سطح بالایی از افراز کد را ارائه میکند. ما میتوانیم تنظیمات پایگاه داده را به طور کامل مورد بازنگری قرار دهیم و نیازی هم به ویرایش منطق تجاری یا EmailSender خود نداریم. وقتی اپلیکیشنها را طراحی میکنیم باید به طور مداوم به مباحث مقیاسپذیری و خوانایی اهمیت بدهیم. استفاده از الگوی مطرح شده در این راهنما موجب افزایش مقیاسپذیری و همچنین ارتقای خوانایی کد میشود.
اگر این مطلب برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای JavaScript (جاوا اسکریپت)
- مجموعه آموزشهای برنامهنویسی
- آموزش جاوا اسکریپت (JavaScript)
- قابلیت های جدید و جالب تایپ اسکریپت ۳.۶ — راهنمای کاربردی
- راهنمای جامع تایپ اسکریپت (Typescript) — از صفر تا صد
==