ترجمه کتاب Node.js by Example – فصل چهارم

ترجمه کتاب Node.js by Example – فصل چهارم

در فصل قبل یاد گرفتیم که چطور منابع لازم برای ساخت برنامه  خود را آماده سازی کنیم. در این فصل کار را پیش می بریم و شروع به نوشتن سطوح پایه‌ای و اصلی برنامه شبکه اجتماعی می کنیم. در این فصل، از الگو طراحی MVC استفاده خواهیم کرد و کد اصلی برای پیاده سازی برنامه در آینده را آماده سازی می کنیم. این‌ها موضوعاتی هستند که در این فصل به آنها خواهیم پرداخت:

  • تبدیل کدهای فصل قبل به یک برنامه با ساختاری با فایل‌های بهتر
  • پیاده سازی یک مسیر (router) که در محیط‌های backend و frontend به خوب کار کند
  • معرفی کوتاه Ractive.js – فریم ورکی که برای بخش سمت کلاینت استفاده می شود
  • توسعه فایل اصلی برنامه
  • پیاده سازی کلاس‌های Controller، View و Model

 

ساخت ترم افزار دشوار است. اغلب پروسه ای است که در حال تغییر است. به منظور توسعه و تکمیل سیستم خود، نیاز به تغییراتی در کدها هستیم. به کدهای فصل قبل مراجعه می کنیم و یک جفت پوشه و فایل‌های جدید ایجاد می کنیم. ما اندکی معماری پروژه را تغییر می دهیم بطوری که در توسعه آینده به مشکل بر نخوریم.

 

ساختار دایرکتوری

این یک روش متداول جداسازی منطق برنامه به frontend و backend است. ما نیز از همین روش استفاده می کنیم. در زیر ساختار پروژه را مشاهده می کنید:

 

دانلود کتاب فارسی node.js

 

دایرکتوری backend شامل فایل‌هایی است که در محیط Node.js مورد استفاده قرار می گیرد. همانطور که دیدم، ما فایل‌هایی که قبلا در دایرکتوری اصلی بودند را به پوشه frontend انتقال دادیم. اینها فایل‌هایی هستند که منابع را در دایکتوری static تولید و ارائه می دهند. ما همچنان به فایل‌های gulpfile.js و package.json و server.js نیاز داریم که شامل کدهای سرور Node.js است.

 

ساخت سرور اصلی برای رسیدگی به request

پیش از این سرور ما تنها به یک درخواست (request) رسیدگی می کرد — متابع (assets). کد زیر چیزی است که سرور ما را در فصل قبل اجرا می کرد:

همراه با assets ما می خواهیم دو چیز دیگر داشته باشیم تا به آنها رسیدگی شود، که در زیر به آنها اشاره شده است:

  • API handler: بخش سمت کاربر برنامه ما قرار است از طریق REST API به backend متصل شود. ما این مفهوم را در فصل دوم توضیح دادیم.
  • Page handler: اگر درخواست‌هایی که به سرور ارسال می شود برای منابع asset یا API خاصی نیست، یکی صفحه HTML ارائه می دهیم که قرار است این صفحه را کاربران نهایی مشاهده کنند.

همانطور که می دانیم این ایده خوبی نیست که تمام چیزها را در یک فایل نگاه داریم. بنابراین، گام اول استخراج تابع assets برای ماژول خود است:

حال ما از این روش مشابه برای ساخت یک فایل backend/API.js استفاده می کنیم. این فایل مسئولیت REST API را بر عهده دارد. ما از فرمت JSON برای انتقال داده ها استفاده خواهیم کرد. ساده‌ترین کدی که می توانیم در ساخت این استفاده کنیم به شرح زیر است:

توجه داشته باشید که تنظیم مقدار صحیح Content-Type مهم است. اگر مقدار غلط یا خالی به آن اختصاص داده شود آنگاه مرورگر که پاسخ (response) را دریافت می کند، شاید به درستی نتیجه را نتواند پردازش کند. در آخر نیز یک رشته خالی JSON ارسال می کنیم.

در آخر، backend/Default.js را اضافه می کنیم. این فایلی است که صفحه HTML تولید می کند تا کاربران آن را در مرورگر خود مشاهده کنند:

 

محتوای Default.js شبیه به API.js است. ما دوباره مقدار Content-Type را مشخص میکنیم و از متد ()end شی response استفاده می کنیم. با این حال، رشته یونیکد HTML را از یک فایل خارجی که در backend/tpl/page.html ذخیره شده است بارگذاری می کنیم. خواندن محتوا بصورت synchronous خواهد بود و تنها یک بار در ابتدا اجرا خواهد شد. در زیر کد page.html آمده است:

این یک boilerplate ساده برای HTML5 است که شامل تگ‌ها body , head , CSS و JavaScript است. برنامه ما تنها به دو فایل جاوا اسکریپت زیر احتیاج خواهد داشت:

  • ractive.js – این فریم ورکی است که در client-side استفاده می کنیم. اطلاعات بیشتر در این مورد در بخش بعدی آمده است.
  • app.js – این جاوا اسکریپت‌ها client-side ما است. همانطور که در بخش قبل دیدیم، توسط Gulp تولید شده است.

با داشتن این رسیدگی‌ها (handler) در backend، آماده هستیم که سراغ کدهایی برویم که در مرورگر اجرا می شوند.

 

پیاده سازی router

تقریبا هر برنامه وبی نیاز به router دارد، جزی از برنامه است که به عنوان یک درب جلویی عمل می کند که کوئری‌های آمده را قبول می کند. مسیر یا router، پارامترهای request ارسال شده را تجزیه و تحلیل می کنند و مشخص می کنند که کدام ماژول از سیستم ما باید به نتیجه رسیدگی کند.

ما از جاوا اسکریپت برای backend (از طریق Node.js) و frontend (تفسیر توسط مرورگر وب) استفاده می کنیم. در این بخش ما یک router خواهیم نوشت که در هر دو سمت برنامه کار کند. اجازه دهید توضیح دهیم چه چیزی برای بخش Node.js نیاز داریم:

Router.js دو ماژول را export می کند. اولی route تعریف کرده که شامل یک مسیر (path) و یک تابع کنترل است که هنگام متناسب بودن URL با مسیر اجرا می شود. تابع check در واقع یک عملیات ساده چک انجام می دهد.

متد add به صورت زیر خواهد بود:

ما می توانیم از پارامتر path چشم پوشی کنیم و فقط تابعی را تعریف کنیم که با همه‌ی مسیرها تطابق داده شود.

تابع check یک مقدار پیچیده است. نه تنها رشته-به-رشته را پوشش می دهد بلکه باید از پاراپترهای پویا نیز پشتیبانی کند. ما از id: برای این پارامترها استفاده می کنیم. به عنوان مثال:

  • home/ – با http://localhost/home تطابق دارد
  • user/feed/ – با http://localhost/user/feed تطابق دارد
  • user/:id/profile/ – این با http://localhost/user/45/profile تطابق دارد
  • user/:id/:action/ – این با http://localhost/user/45/update تطابق دارد

به منظور پیاده سازی این قابلیت ما از reqular expression ها به صورت زیر استفاده می کنیم:

اجازه دهید تابع را خط به خط توضیح دهیم. آرگومان‌های متد شامل f و params است. fragment در واقع یک مسیر است. این آدرس URL است که برای بررسی کردن اجرا می کنیم. در متد add، ما یک کنترلر اضافه کرده ایم که باید متناسب با مسیر یک بار اجرا شود. این خیلی خوب میشد اگر قادر به ارسال متغیرهای اضافی به این متد می شدیم. پارامتر params این قابلیت را پوشش می دهد. ما می توانیم یک آرایه ارسال کنیم که بعدا به پارامترهای کنترلر ترجمه شوند.

تابع با بررسی هر آنچه که fragment تعریف کرده است کار را ادامه می دهد. در میحط Node.js، باید URL را ارسال کنیم.

 

هدف اصلی از این تابع، دریافت URL فعلی مرورگر با استفاده از شی سراسری window.location است. شما شاید توجهتان به تابع clearSlashes جلب شده باشد. این عملی را انجام می دهد که از نام تابع آن پیداست.- اسلش‌های غیر ضروری را پیدا و از ابتدا و انتهای رشته حذف می کند:

اجازه دهید به تابع check برگردیم. ما همچنان حلقه‌ای را بر روی مسیرهای ثبت شده تعریف کرده ایم. ما برای هر مسیر (route) عمل زیر را انجام می دهیم:

  • ما یک regular expression تعریف کرده ایم که بخش‌های پویا را استخراج کند (اگر وجود داشته باشد). به عنوان مثال users/:id/:action به test/([\w-]+)/([\w-]+) تبدیل می شود. ما از این در ادامه کتاب استفاده می کنیم.
  • ما با استفاده از regular expression هر چیزی که در fragment پیدا شود را بررسی می کند. اگر وجود داشته باشد آنگاه یک آرایه از پارامترها را تعریف می کنیم و کنترلر را فراخوانی می کنیم.

جالب است که اگر ما مسیر خود را پاس دهیم (fragment). ما از همین جاوا اسکریپت در محیط‌های Node.js و مرورگر استفاده می کنیم.

بخش client-side برنامه به دو متد دیگر نیاز دارد. تا اینجا ما مسیرهایی را تعریف و هر آنچه را که این قوانین بطور خاص با URL مطابقت دارد را بررسی می کند. این شاید در backend کار کند اما در frontend نیاز داریم بطور مداوم موقعیت مروگر را زیر نظر بگیریم. به همین خاطر تابع زیر را اضافه می کنیم:

با استفاده از setInterval قادر خواهیم بود fn را که یک closure است را بارها و بارها اجرا کنیم. بررسی می کند که URL فعلی تغییر کرده است یا نه، و اگر اینطور است متد check را اجرا کند، که قبلا نیز این متد توضیح داده شده است.

آخرین چیز که به کلاس اضافه شده است تابع navigate است:

ما شاید بخواهیم صفحه فعلی خود را داخل کد تغییر دهیم. router ابزاری عالی برای این کار است. وقتی آدرس URL تغییر می کند، کلاس بطور خودکار کنترلر درست صحیح را فراخوانی می کند. کد قبل از API تاریخچه در HTML5‌ استفاده می کند (http://diveintohtml5.info/history.html). متد pushState رشته‌ی آدرس بار مرورگر را تغییر می دهد.

با اضافه کردن متد navigate، مسیرهای ما به مرحله پایانی رسیدند، از این ماژول می توان به عنوان backend در frontend استفاده شود. قبل از اینکه اجزاء Model-View-Controller را ادامه دهیم، می خواهیم بطور مختصر Ractive.js را معرفی کنیم — فریم ورکی که برای نیرو بخشیدن به توسعه رابط کاربری استفاده می کنیم.

 

معرفی Ractive.js

Ractive.js فریم ورکی است که توسط TheGuardian توسعه داده می شود، بله همان سازمان خبری شناخته شده (http://www.theguardian.com). به سادگی می توان با DOM تعامل داشته باشیم و ویژگی‌هایی مثل binding دوطرفه و ایجاد اجزا سفارشی را شامل می شود. ما قرار نیست تمام این ویژگی‌های فریم ورک را در حال حاضر پوشش دهیم. ویژگی‌های جدید در فصل‌های بعدی معرفی می شوند.

در برنامه های پیچیده ای مثل برنامه ما، بسیار مهم است که منطق برنامه را به اجزا دیگر تقسیم کنیم. خوشبختانه Ractive.js رابطی را فراهم می کند که این کار را انجام می دهد. در زیر یک جزء معمولی را مشاهده می کنید:

مشخصه‌ی template شامل HTML یا (در این مورد) یک قالب از پیش تعریف شده است. داده‌های شی در داخل قالب ما قابل دسترسی است. Ractive.js از mustache به عنوان یک template engine استفاده می کند. ما همچنین می توانیم مشخصه‌ی دیگری به نام el تعریف کنیم و بطور مستقیم انتخاب کنیم که چه اجزایی می بایست بعد از مقداردهی اولیه نمایش داده شوند. با این حال راه دیگری نیز وجود دارد — متد render. این متد یک عنصر DOM قبول می کند. در کد قبلی، این تنها بدنه صفحه است.

به طور مشابه، مثل درخت در DOM مرورگر، به اجزای تودتو نیاز داریم. این بخوبی با تعریف یک تگ سفارشی در فریم ورک انجام می شود، همنطور که در مثال زیر آمده است:

هر اجزا می تواند یک شی hash map داشته باشد که تگ‌های سفارشی ما را تعریف کند. ما می توانیم هر تعداد که می خواهیم اجزا داشته باشیم. HTML تولید شده توسط کد قبل بصورت زیر است:

چندین روش مختلف برای برقراری ارتباط بین اجزای مختلف Ractive.js‌ وجود دارد. یکی از متداولترین روش‌ها trigger و listen کردن رویدادها (event) است. بیایید تکه کد زیر را بررسی کنیم:

ما در اینجا چند مفهوم جدید داریم. ابتدا یک تابع عمومی notifyTheOutsideWorld تعریف کردیم. Ractive.js این اجازه را می دهد که متدهای سفارشی خود را داشته باشیم. با متد on ما یک رویداد مشخص را مشخص می کنیم و با fire رویداد را اجرا می کنیم.

در مثال قبل ما از متدی استفاده کردیم که آن را توضیح ندادیم. تابع set داده‌های شی اجزا را تغییر می دهد. ما از این تابع بطور مرتب استفاده خواهیم کرد.

آخرین چیز که در مورد Ractive.js در این فصل به آن اشاره خواهد شد، تابعی است که تغییرات در اجزای دادهای خواص را نظاره می کند.کد زیر نشان می دهد که تابع نظاره کنند چطور خواص title را زیر نظر می گیرد:

این کد یک پنجره alert با مقدار !Hello نشان می دهد. اجازه دهید ادامه کار را با تعریف فایل اصلی برنامه یا به عبارت ساده نقطه ورود client-side یه شبکه اجتماعی ما ادامه دهیم.

 

ساخت نقطه ورود برنامه

در حالی که Gulp را قبلا راه اندازی کرده ایم، یک task برای بسته بندی جاوا اسکریپت ایجاد می کنیم.Browserfy به یک نقظه ورود برای حل وابستگی ها نیاز دارد. ما frontend/js/app.js را تنظیم می کنیم. به طور مشابه، برای backend سراسر router خود را خواهیم ساخت. کد زیر دو route و یک تابع کمکی برای نمایش اجزا Ractive.js در صفحه فراهم می کند:

ما در فوق به متغیر Router نیاز داریم. در کنار این نیز به یک کنترلر نیاز داریم تا مسئولیت صفحه اصلی را بر عهده داشته باشد. درباره این موضوع در بخش بعدی یاد خواهیم گرفت. اما برای حالا، تنها کافیست بدانیم این یک کامپوننت Ractive.js است.

ما نمی‌خواهیم هر جاوا اسکریپتی را تا وقتی که صفحه کاملا بارگذاری شده است را اجرا کنیم. پس کد راه انداز خود را در window.onload قرار می دهیم. نگاه دارنده اجزای Ractive.js تگ body خواهد بود و یک مرجع به آن خواهیم ساخت. ما همچنین یک تابع کمکی showPage تعریف می کنیم که وظیفه نمایش صفحه فعلی و اطمینان حاصل کردن از اینکه آخرین صفحه ای که اضافه شده است به درستی حذف شده است یا نه! متد teardown یک تابع توکار در فریم ورک است. این متد اجزا را نمایش نمی دهد و تمام هندلرهای رویدادها را حذف می کند.

برای این فصل، ما تنها یک صفحه خواهیم داشت — صفحه اصلی. ما از router که در backend درست کرده ایم استفاده می کنیم و مسیر home/ را ثبت می کنیم. دومین هندلری که به تابع add ارسال می کنیم اساسا هیچ مسیری نیست که تطابق داشته باشد. کاری که بلافاصله انجام داده ایم انتقال کاربر به آدرس home/ است.در بخش بعدی اولین کنترلر خود را خواهیم ساخت – کاموننتی که کنترل صفحه اصلی ما را بر عهده خواهد داشت.

ایجاد یک Controller

نقش کنترلر در این زمینه هماهنگی صفحات است. به عبارتی، وظیفه کنترلر بارگذاری صفحات است.  محتوای فایل controllers./Home.js به صورت زیر است:

قبل از اینکه به سراغ propertyهای قالب ها و کامووننت ها برویم، باید چند کلمه ای درباره onrender بگوییم. کامپوننت‌های Ractive.js یک رابط برای تعریف پروسه‌هایی که در داخل هر یک از اجزای  هندلر برای پردازش  ارائه

به عنوان مثال، ما نیاز به انجام برخی از اقدامات در هر بار نمایش اجزا در صفحه داریم. همچنین، onconstruct و onteardown و onupdate وجود دارد. این قطعا یک روش خوب برای پیاده سازی منطق برنامه است. تمام امثال این مشخصه‌ها را در وب سایت رسمی فریم ورک آمده است – http://docs.ractivejs.org/latest/options

همچنین مشخصه template را در حال معرفی Ractive.js بودیم توضیح دادیم. به هر حال، در کد زیر ما نیازی به رشته به عنوان مقدار نداریم. ما نیاز به یک فایل جاوا اسکریپت دیگر داریم — یک قالب HTML از پیش تعریف شده. از پیش تعریف شده توسط Gulp بصورت زیر انجام داده می شود:

ما همچنین به تمام فایل‌های HTML در دایرکتوری frontend/tpl دسترسی داریم و آنها را به فایل‌های JavaScript تبدیل می کنیم تا Ractive.js و Browserify قابل فهم باشد. در آخر Gulp با همان نام و دایرکتوری اما با پسوند متفاوت ایجاد می کند. به عنوان مثال، قالب برای صفحه اصلی ما می تواند به صورت زیر باشد:

با اجرا دستور gulp در ترمینال قادر خواهیم بود محتوای زیر را در frontend/tpl/home.js مشاهده کنیم:

نیازی نیست کاملا درک کنیم چه اتفاقی در این مشخصه‌ها رخ می دهد. جاوا اسکریپتی که به HTML تبدیل می شود برای فریم ورک محفوظ است.

اگر قالب و اجزای تعریف شده در کد قبل را بررسی کنید خواهید دید که دو زیر‌کامپوننت داریم – navigation و appfooter. اجازه دهید ببینیم آنها چطور کار می کنند.

 

مدیریت View

توجه کنید view اجزا Ractive.js است. آنها قالب خودشان را دارند. در واقع ماژول Home.js می تواند به عنوان یک view باشد. الگو MVC معمولا در مرورگر تبدیل شده است و نیازی به تعریف دقیق آنها نیست. در این مورد از برنامه ما چون ما در حال استفاده از فریم ورک هستیم که برخی از قوانین و قابلیت های آن با MVC معمول سازگار نیست. البته با وجود این هیچ مشکلی وجود ندارد. تا وقتی که تقسیم مسئولیت های برنامه ما در شکل خوب باشد.

navigation بسیار آسان است. تنها قالبی را تعریف می کند تا نمایش داده شود:

به منظور ساخت چیزی جالب‌تر و به موجب تعریف مدل‌ها، ما شماره نسخه را در footer نمایش می دهیم. این عدد از مدل ساخته شده در models/Version.js می آید. کد زیر برای فایل views/Footer.js است:

قبل از اینکه توضیح دهیم چه اتفاقی برای bindComponent می افتد، اجازه دهید بررسی کنیم چه چیزی در tpl/footer.html داریم:

ما یک متغیر پویای version را داریم. در این مورد ما قرار نیست از مدل استفاده کنیم، ما آن را در مشخصه data از کامپوننت تعریف می کنیم یا از this.set(‘data’, value) استفاده می کنیم. با این حال، ماژول FooterModel کار ما را آسان‌تر کرده است. به این خاطر است که از ماژول bindComponent استفاده می کنیم. متد fetch در بخش بعدی، همزمان‌سازی داد‌ه‌های مدل با دادهای backend خواهید دید.

 

ایجاد یک Model

ما احتمالا چندین مدل خواهیم داشت که تمام آنها به یک صورت در متد به اشتراک گذاشته می شود. بطور معمول مدل‌ها درخواست‌های HTTP را برای سرور ایجاد می کنند و آنها را دریافت می کنند. بنابراین، این چیزی است که نیاز داریم Abstract باشد. خوشبختانه، Ractive.js این امکان را فراهم می کند تا کامپوننت‌ها را توسعه دهید. در اینجا کد فایل models/Version.js را مشاهده می کنید:

ما models/Base.js را داریم، فایلی که شامل این توابع هستند. این قرار است یک کلاس مادر باشد تا بعدها به ارث برده شود.

ما دو متد را تعریف کردیم — fetch و bindComponent. اولی برای کمک در عملیات Ajax استفاده می شود. در حال حاظر قصد نداریم به جزئیات آن بپردازیم. این شبیه به متد ajax. در جی کوئری است و یک الگوی رابط پیاده سازی می کند.

کامپوننتی که ماژول Base را توسعه می دهد باید یک URL ارائه دهد. این نقطه‌ای است که مدل درخواست (request) ایجاد می کند. در این مورد، api/versio/ است. backend ما محتوای این آدرس URL را بررسی می کند.

اگر به عقب برگردیم و چک کنیم که چه کاری را با api/ انجام داده ایم، خواهیم دید که نتیجه تنها یک شی خالی است. اجازه دهید این را تغییر دهیم و پیاده سازی مسیر api/version/ را پوشش دهیم. ما backend/API.js را بصورت زیر تغییر می دهیم:

ما از router یکسان برای ترسیم URL برای پاسخ خاص استفاده می کنیم. بنابراین، بعد از این تغییرات، مدل ما شامل مقدار 0.1 خواهد بود.

در آخر، اجازه دهید سحر و جادویی که در تابع bindComponent رخ می دهد را بررسی کنیم:

ما مشخصه‌ی محلی data زیر نظر می گیریم، به عبارت ساده می خواهیم وقتی متغیر data مقدارش تغییر کرد ما از آن با خبر شویم. این بعد از فراخوانی متد fetch به روزرسانی می شود. مقدار جدید به هندلر پاس داده می شود و سادگی می توانیم متغیر‌ها را به کامپوننت‌ها ارسال کنیم. این‌ها فقط چند خط کد است، اما با این حال آنها موجب تشکیل یک Abstract خوب می شوند. در تعریف مدل واقعی، تنها نیاز داریم URL را مشخص کنیم. ماژول Base مسئول این کار است.

 

خلاصه

در این فصل پایه اصلی برنامه خود را بنا کردیم. ما همچنین router که پایه اصلی سیستم ما است را نیز ایجاد کردیم. حال کنترلرها به خوبی محدوده مسیرها را می شناسند و viewهای نمایش داده شده در صفحه، بطور خودکار تغییراتی که در مدل حاصل شده است را به روز رسانی می کنند. همچنین یک مدل ساده را معرفی کردیم که داده ها را از API ساخته شده در backend دریافت می کند.

در فصل بعدی ویژگی‌های اصلی را پیاده سازی می کنیم — قصد داریم کاربران سیستم خود را مدیریت کنیم.