بخش اول : آشنایی با مفهوم میکروسرویس
بخش دوم : ویژگی های اصلی یک میکروسرویس
بخش سوم : تحلیل یک پروژه کوچک بر اساس میکروسرویس ها
بخش چهارم : شروع پیاده سازی یک پروژه فروشگاهی
پیشنیاز 1 بخش پنجم : آموزش Node و Typescript برای تولید api
پیشنیاز 2 بخش پنجم : آموزش داکر و مفاهیم اولیه
بخش پنجم : پیاده سازی یک میکروسرویس برای نمایش کالاها (همین مقاله)
پیشنیاز 1 بخش ششم : آشنایی با Asp.net core 6
بخش ششم : پیاده سازی یک میکروسرویس برای کار با سبد خرید
بخش هفتم : پیاده سازی یک میکروسرویس برای محاسبه قیمت و تخفیفات
بخش هشتم: پیاده سازی یک میکروسرویس برای ثبت سفارش
برای دیدن ویدیوهای من در مورد برنامه نویسی عضو این کانال شوید :
https://t.me/mediapub_channel
بخش چهارم را به عنوان مقدمه برای این بخش بخوانید.
میخواهیم به کمک یک پروژه Node و با استفاده از ابزار Typescript و دیتابیس Mongo یک پروژه ساده برای دسترسی و تغییر کالاها ایجاد کنیم و در نهایت به کمک داکر و docker-compose نحوه داکرایز کردن این مجموعه را بررسی کنیم. اگر برنامه نویس جاوااسکریپت نیستید اصلا نگران نباشید، ذهن خودتونو آزاد کنید و با من همراه باشید تا به کمک Node (ابزار ران تایم جاوااسکریپت) یک پروژه کوچک طراحی کنیم.
دقت کنید که تخصص در زبان برنامه نویسی جاوااسکریپت برای خواندن این مقاله لازم نیست.
با مطالعه این مقاله، روش داکرایز کردن را برای پایتون و دات نت و php و ... هم یاد میگیرید ثانیا در مقالات بعدی با فریم ورک ها و زبان های برنامه نویسیِ مختلف میکروسرویس مینویسیم و داکرایز کردن آن ها را نیز بررسی میکنیم.
انتظار من این است که خواننده بعد از مطالعه این نوشته بتواند موارد زیر را به راحتی تجزیه و تحلیل کنید :
اگر با Node js آشنایی ندارید و نمیدانید چطور یک پروژه Node/Typescript بسازید این مقاله را بخوانید و سپس ادامه نوشته را پیگیری کنید. من این پروژه را به شکل آماده در Github قرار داده ایم (این لینک). برای دسترسی به تمپلیت اولیه که در مقاله فوق آن را پیاده کرده ایم کامیت اول ریپازیتوری موجود در github را ببینید.
برای دسترسی به دیتابیس در پروژه Node از Mongoos استفاده میکنیم که بهتر است در محل کار آن را مانگوووس (با تکید روی س) صدا کنید. (برای خودتون میگم).
بدون فوت وقت به ایجاد یک CRUD ساده برای کار با کالا می پردازیم.(Create/Read/Update/Delete) قبل از هر چیز روش ارتباط با Mongo را در یک پروژه Node/Typescript/Express بررسی میکنیم.
1. مطمئن شوید که mongo را نصب کرده اید. (برای انجام جزئیات این مرحله، این مقاله را بخوانید، در انتهای مقاله به روش نصب mongo از طریق داکر اشاره شده است)
2. روی پروژه ای که به شکل تمپلیت از Node/Typescript/Express تهیه کرده ایم کتابخانه Mongoos را نصب کنید.
npm i mongoose
عبارت فوق را در ترمینال Visual code مینویسیم (فرض من این است که شما مقاله مربوط به ساخت تمپلیت Node با Typescript را مطالعه کرده اید. در حقیقت ما در حال تکمیل آن پروژه هستیم)
3. برای اینکه تایپ اسکریپت یک دسترسی خوش ساخت به mongoos داشته باشد و بتواند با interface ها به ما کمک کند (متدهای دسترسی را با hint و intellisense ببینیم)، defenition type مربوط به mongoos را به کمک دستور زیر نصب میکنیم.
npm i --save-dev @types/mongoose
از save-dev برای این استفاده میکنیم که ابزار توسعه را از کتابخانه های کاربردی جدا کنیم. این اتفاق در package.json می افتد. دو بخش جدا برای dependencies و devdependencies درست میشود. ابزار توسعه نیازی به انتقال حین build شدن ندارند. یعنی بعد از build، وجود این type ها ارزشی ندارد.
4.کنترلر و روت جدیدی در پروژه تمپلیت میسازیم تا لیست کالا ها را دریافت کنیم.
در فایل product.ts موجود در فولدر controllers متدی که لیست کالاها را برگرداند میسازیم. فعلا آن را پیاده سازی نمیکنیم.
روت getAllProducts را در فایل product.ts موجود در فولدر routes تعریف میکنیم که به controller.getAllProducts متصل است.
در فایل server.ts هم یک روت جدید برای ارتباط با فایل route بالا تعریف میکنیم. روت های جدید را فقط در فایل routes/product.ts اضافه میکنیم و همه به نوعی Sub-Route آنچیزی هستند که در server.ts تعریف کرده ایم.
5. به کانفیگ پروژه اطلاعات اتصال به مانگو را اضافه میکنیم. MONGO_OPTIONS و MONGO_HOST و MONGO ثوابتی هستند که ایجاد کردیم و در نهایت ثابت MONGO را به ثابت CONFIG در پایین فایل config.ts اضافه میکنیم.
6. در ادامه میخواهیم در فایل Server.ts به Mongo بوسیله Mongoose متصل شویم و کانکشن برقرار کنیم.
7. فولدری به نام interfaces میسازیم. میخواهیم مدلی را بسازیم که به کمک آن مانگوس از ساختار product مطلع شود.
8. حالا اسکیمای product را در فولدر models میسازیم.
timestamps : به طور خودکار تاریخ را در داکیومنت مونگو ثبت میکند
9. حالا کنترلر product را تکمیل میکنیم.
10. متد list را تست میکنیم. قبل از تست لازم است پروژه را با دستور زیر اجرا کنیم :
nodemon source/server.ts
11. با فشردن ctrl+c در ترمینال visual code میتوانیم اجرا را متوقف کنیم. متد createPost را برای ایجاد آیتم در دیتابیس درست کرده ایم. همچنین متدهای لازم برای یک crud را مینویسیم. برای این متدها روت های مناسب را در فایل routes/product.ts تعیین میکنیم.
12. پروژه را با دستور شماره 10 دوباره اجرا کنیم.
13.برای تست متد add چون POST است از ابزاری مثل Postman استفاده میکنیم.
بخش هایی که علامت گذاشتیم را به همین شکل در پستمن تکمیل میکنیم و ورودی را به شکل فوق مینویسیم و خروجی نشان میدهد که در دیتابیس یک رکورد (داکیومنت) ثبت شده است.
میتوانید به کمک ابزار دیگری مثل compass یا Studio 3T محتوای دیتابیس را چک کنید
14. با تست put خواهیم دید.
به اهدافی که تعیین کرده بودیم در پروژه رسیدیم. CRUD با موفقیت اجرا میشود.
قبل از هرکاری مطمئن باشید vpn شما متصل است. داکر برای گرفتن کتابخانه های مورد نیاز گاهی بدون وی پی ان به مشکل میخورد.
ابتدا فایل داکر را میسازیم.
برای ساخت فایل ایمیج دستور زیر را در محلی که فایل package.json قرار دارد مینویسیم:
docker build . -t catalogapi
و برای ساختن کانتینر از این ایمیج از دستور زیر استفاده میکنیم و پورت 4300 داخلی را به 4500 خارجی مپ میکنیم (catalogapi اولی اسم کانتینر و دومی اسم imageی است که کانتینر از روی آن ساخته میشود):
docker run -d -p 4500:4300 --name catalogapi catalogapi
و میبینیم که پروژه اجرا میشود.
برای دیدن لاگ ِ کانتینر دستور زیر را مینویسیم:
docker logs -f a18b3a98e3fa
مقدار a18b3a98e3fan شناسه کانتینر ماست که به کمک دستور docker ps قابل دستیابی است.
docker ps
خروجی لاگ یک خطا نشان میدهد.
خطای فوق یعنی سرویس ما نمیتواند به monogo متصل شود و طبیعیست. چون ما پروژه را داکرایز کردیم و در کانتینر ما، فولدر build داخل یک سیستم لینوکسی قرار دارد و در داخل یک سیستم لینوکسی پروژه اجرا شده است. پورت 4300 که پورت اجرا شده داخلی است را به 4500 خارجی مپ کردیم که از بیرون به api ها دسترسی داشته باشیم. با این اوصاف واضح است این کانیتنر (یعنی catalog) به mongo که خودش یک کانتینر دیگر است دسترسی ندارد و همدیگر را نمیبینند. ( در مرحله 1 در این مقاله ما mongo را داکرایز کردیم).
یک راه حل برای این مشکل مشخص کردن نام یک container برای container دیگر است. در تصویر فوق صراحتا نوشته شده که ارتباط با 127.0.0.1:27017 ممکن نیست ولی حتما باید ارتباط با mongo:27017 ممکن باشد (فرض کنید mongo اسم کانتینر مربوط به مانگو است) پس در کد به جای 127.0.0.1 اسم کانتینر را مینویسیم. اما بهتر است اینکار را با تعریف environment انجام دهیم. در کد نیز این قابلیت (استفاده از environment ها ) را به نام MONGO_URL لحاظ کرده ایم. پس کافیست موقع اجرای ایمیج مربوط به catalog.api این کلید از environment را تعریف میکنیم.
docker run -d --network node-webapp-network --name mongodb mongo docker run -d -p 4500:4300 --env 'MONGO_URL=mongodb:27017' --network node-webapp-network01 --name catalogapi catalogapi
دستور اول، داکرِ مربوط به ایمیجِ mongo را به نام mongodb اجرا میکند.
دستور دوم داکر مربوط به ایمیج ِcatalogapi را به نام catalogapi اجرا میکند و یک آیتم environment vatraible هم به نام MONGO_URL تعریف میکند و مقدار میدهد.
نکته مبهم ولی ساده در این دو دستور آپشن network است که باید در هر دو یکسان باشد، یعنی هر دو در یک شبکه باشند که بتوانند همدیگر را ببینند. این شبکه را باید قبل از اجرای دو دستور فوق با دستور زیر بسازیم.
docker network create node-webapp-network
اما راه حل ساده تر برای یکسان سازی شبکه و همینطور مدیریت ارتباط بین میکروسرویس ها استفاده از docker-compose است.
داکر کامپوز مسئول ایجاد، اجرا و مدیریت ارتباط بین میکروسرویس هاست. چون برای همه است، پس محل قرار گیری آن نباید داخل فولدر این میکروسرویس باشد بلکه یک سطح عقب تر باید قرار داشته باشد.(که برای دسترسی به میکروسرویس های دیگر به زحمت نیفتیم و به میکروسرویس های دیگر هم دسترسی داشته باشد)
یک فایل docker-compose.yaml با بخش های اصلی میسازیم و برای محیط های مختلف مثل development یا production این فایل را override میکنیم. چون فعلا هدف ما development است یک فایل docker-compose.override.yaml هم میسازیم.
محتوای یک فایل کامپوز به شکل زیر است.
همانطور که میبینید ما به ازای هر سرویس یک سکشن داریم. فعلا دو سرویس یکی مخصوص دیتابیس مونگو و دیگری مخصوص به catalog داریم که نامگذاری دلخواه میکنیم و در docker-compose و docker-compose.override آنها را به شکل فوق تکمیل میکنیم.
نکات :
بخش volumes برای سکشن هایی که نیاز به ذخیره اطلاعات روی دیسک دارند لازم است. درادامه این بخش را توضیح خواهم داد.
در مورد volume در این مقاله به اختصار صحبت کردیم. گفتیم چون کانتینری که از ایمیج ساخته میشود به لحاظ پایداری مطمئن نیست و ممکن است Stop یا Remove شود (توسط ما یا به دلیل مشکلات نرم افزاری) بهتر است دیتاها را در جای دیگری نگه داریم. مثلا اگر سرویس آپلود عکس داریم دلیلی ندارد در خود کانتینر عکس ها را نگه داریم، چون با حذف کانتینر، تصاویر نیز از بین میروند.
مثلا فرض کنید داخل کانتینر یک فولدر images داریم که عکس ها در آن قرار میگیرند. کار درست این است که این فولدر را به یک فولدر خارجی یا یک docker volume خارجی مپ کنیم که هر اتفاقی در فولدر images افتاد در آن فولدر هم رخ دهد و ما دیتا را در یک جای مطمئن داشته باشیم. در مورد دیتابیس ها نیز به همین شکل عمل میکنیم و فایل های db را به کمک volume یا bind mount به یک محیط خارجی مپ میکنیم.
در تصویر بالا volume و bind mount را میبینید، یک حالتِ tmpfs هم داریم که اطلاعات را در memory نگه میدارد و دسترسی به آن سریع است، اما به لحاظ پایداری و حجم دیتا باید ملاحظاتی را در نظر بگیریم که در این مقاله فرصت پرداختن به آن نیست.
در فایل docker compose اگر قرار باشد از حالت docker volume استفاده کنیم یک سکشن به نام Volume در سطح اول آن تعریف میکنیم و اسم ولیوم هایی که میخواهیم بسازیم را مشخص میکنیم. حالا در سکشن مربوط به دیتابیس این docker volume ها را به آدرس های داخل کانتینر مپ میکنیم. در mongo محل ذخیره سازی اطلاعات دیتابیس و کانفیگ ها فولدرهایی هستند که میبینید:
با دستور زیر هم میتوانیم لیست volume ها را ببنیم
docker volume ls
اگر هم بخواهید به شکل bind mount عمل کنید یعنی یک فولدر روی دیسک را به فولدری داخل کانتینر مپ کنید از روش زیر عمل کنید.
در این حالت دو فولدر در مسیرهای داده شده روی سیستم عاملِ Host تعریف میکنیم(چون من روی ویندوز کدنویسی میکنم برای توسعه فعلا دو فولدر در درایو c معرفی کرده ام که در تصویر میبینید). به محض اجرای داکر کامپوز، این دو فولدر ساخته میشوند و محتوای فولدرهای متناظر داخل کانتینر درون این فولدرها ساخته میشود. دقت کنید که دیگر داکر، volume مشخص برای اینکار نمی سازد.(در داکر دسکتاپ بخش Volume اطلاعاتی از این فولدرها نمایش نمیدهد)
اگر نوع ذخیره سازی شما از نوع docker volume است (و Bind mount نیست)، اگر میخواهید محتوای volume های درست شده روی ویندوز را ببینید از آدرس زیر در پنجره explorer استفاده کنید:
\\wsl$\docker-desktop-data\version-pack-data\community\docker\volumes\
بطور کل در هر دو حالت شما این مزیت را بدست آوردید که فایل های دیتابیس شما در محلی فیزیکی روی سیستم ذخیره شده که حالا یا به شکل docker volume است و یا به شکل یک فولدر قابل دسترسی(bind mount) است. هر اتفاق بدی برای کانتینر شما بیفتد دیتای شما از بین نخواهد رفت. هر بار کانتینر را از بین ببرید و دوباره بسازید میتوانید به این دیتا دسترسی داشته باشید به شرط آنکه در فایل docker-compose مسیر ها را درست تعریف کنید.
برای اجرای docker-compose ابتدا مطمئن میشویم کانتینر برای تست از روی ایمیج ها نداشته باشیم. بهتر است قبلیها را پاک کنیم.
حالا در فولدری که docker-compose وجود دارد دستور زیر را مینویسیم
docker-compose -f .\docker-compose.yml -f .\docker-compose.override.yml up -d
همیطور با دستور زیر میتوانید سرویس ها را down کنید و کانتینرها ابتدا stop و سپسremove میشوند.
در این بخش نه تنها توانستیم اولین میکروسرویس خود را بسازیم بلکه با ایجاد docker-compose هم آشنا شدیم. پیش از این گفتیم docker-compose مختص این میکروسرویس نیست، به همین دلیل آن را خارج از فولدر این پروژه قرار دادیم. در بخش های بعد ما میکروسرویس های دیگری خواهیم نوشت که وظایف مختلفی دارند اما همه آنها با تکمیل همین docker-compose به مرحله عملیاتی و اجرا خواهند رسید.
کدهای مربوط به این بخش را در این ریپازیتوری از گیتهاب ببنید (کامیت پایانی تا اینجا "add and modify docker compose")