مرتضی دلیل
مرتضی دلیل
خواندن ۱۹ دقیقه·۳ سال پیش

آموزش میکروسرویس Microservice - پیاده سازی یک میکروسرویس برای محاسبه قیمت (بخش هفتم)

بخش اول : آشنایی با مفهوم میکروسرویس
بخش دوم : ویژگی های اصلی یک میکروسرویس
بخش سوم : تحلیل یک پروژه کوچک بر اساس میکروسرویس ها
بخش چهارم : شروع پیاده سازی یک پروژه فروشگاهی
پیشنیاز 1 بخش پنجم : آموزش Node و Typescript برای تولید api
پیشنیاز 2 بخش پنجم : آموزش داکر و مفاهیم اولیه
بخش پنجم : پیاده سازی یک میکروسرویس برای نمایش کالاها
پیشنیاز 1 بخش ششم : آشنایی با Asp.net core 6
بخش ششم : پیاده سازی یک میکروسرویس برای کار با سبد خرید
بخش هفتم : پیاده سازی یک میکروسرویس برای محاسبه تخفیفات (همین مقاله)
بخش هشتم: پیاده سازی یک میکروسرویس برای ثبت سفارش


برای دیدن ویدیوهای من در مورد برنامه نویسی عضو این کانال شوید :
https://t.me/mediapub_channel

در این بخش میخواهیم یک میکروسرویس دیگر به منظور استخراج قیمت کالا و تخفیفات مرتبط با کالا پیاده سازی کنیم.

فرض کنید یک جدول به نام Price در این میکروسرویس داریم که شناسه کالا و قیمت کالا را در خود نگه میدارد. همچنین این جدول به ازای هر کالا چندین قیمت در تاریخ های مختلف دارد پس برای استخراج قیمت کالا ما نیاز به آخرین قیمت آن کالا داریم. میتوانستیم یک جدول برای درج تخفیف به نام Discount تعریف کنیم که هر کالا تخفیف های مختلفی بر اساس کد تخفیف داشته باشد و قابلیت فعال یا غیرفعال بودن را نیز تعریف کنیم. ده‌ها راهکار و روش برای پیاده سازی قیمت و تخفیف بر اساس مناسبت و کاربر و تعداد خرید و ... وجود دارد که طبیعتا در این مجموعه مقالات فرصت پرداختن به روش های قیمت گذاری و تخفیف نیست.

میخواهیم فقط یک جدول ساده قیمت به نام Price ایجاد کنیم و به کمک دات نت api های مورد نیاز را بنویسیم. با سناریویی که در این مقاله تعریف شده این میکروسرویس باید به یک میکروسرویس دیگر خدمات ارائه دهد. یعنی به کمک gRPC که به آن میپردازیم، این میکروسرویس صدا زده میشود و قیمت کالا را در اختیار میکروسرویس دیگر قرار میدهد. طبیعتا ایجاد و ویرایش و حذف قیمت برای یک کالا میتواند در همین میکروسرویس و به شکل Rest Api انجام شود و تبدیل api های آن به gRPCبی فایده است چون پنل مدیریت این سایت مشکلی برای فراخوانی مستقیم Http Request ها یا همان api های REST ندارد.

پس در نظر داشته باشید این میکروسرویس هم به بیرون ( رابط کاربری پنل مدیریت) و هم به میکروسرویس داخلی (میکروسرویس Basket ) خدمات خواهد داد.

انتظار من این است که خواننده بعد از مطالعه این نوشته بتواند موارد زیر را به راحتی تجزیه تحلیل کنید :

  • ایجاد یک پروژه دات نت
  • آشنایی با PostgreSQL
  • آشنایی با PgAdmin
  • آشنایی با Dapper
  • ایجاد یک CRUD ساده با دات نت
  • آشنایی به Swagger و استفاده از آن به جای Postman
  • آشنایی با gRPC
  • آشنایی با فایل ProtoBuf
  • پیاده سازی gRPC داخل یک پروژه web Api
  • آشنایی با BloomRpc
  • آشنایی با grpCurl
  • روش override کردن محتوای appsettings در فایل تنظیمات docker-compose


استفاده از PostgreSQL

پُستگره، یک دیتابیس اوپن سورس و رایگان و ریلیشنال با ویژگی ها و فواید قدرتمند است.

به دلیل امنیت ، مقیاس پذیری و اطمینان در ذخیره سازی، از پُستگره به عنوان یک ابزار مدیریت دیتابیس در بسیاری از جاها استفاده میکنند. به دلیل دقت، قدرت و شکل معماری دیتا یکی از سیستم های مدیریت دیتابیس مورد قبول در این روزهاست.

این دیتابیس نسبت به mysql و بقیه دیتابیس های هم رده سرعت insert و update کندتری دارد. چون یک دیتابیس Transaction based است.
یک تیبل Price خواهیم ساخت که اطلاعات قیمت را در آن نگهداری میکنیم.
میخواهیم تنظیمات و نصب Postgre را به کمک داکر انجام دهیم.

طبق روال قبل که در مقالات پیشین به آن اشاره شد نسخه ایمیج رسمی این دیتابیس را پیدا کرده و pull میکنیم و روی داکر سیستم شخصی کانتینر آن را میسازیم.

برای دریافت این ایمیج دستور زیر را مینویسیم

docker pull postgres

برای اجرای این ایمیج و تبدیل آن به کانتینر از دستور زیر استفاده میکنیم. یوزر را postgres و پسورد را 123456 تعیین میکنیم :

حالا چک میکنیم این دیتابیس درست نصب شده باشد و قابل استفاده باشد :

حالا به کمک ابزاری که روی Visual Code نصب کردیم مثل مقاله قبلی با این دیتابیس هم ارتباط برقرار میکنیم

خب اتصال از این روش با موفقیت برقرار شده است.

اگر میخواهید خیلی رسمی تر به این دیتابیس متصل شوید و از محیط GUI مرسوم استفاده کنید، ایمیج PGAdmin را به شکل زیر نصب کنید:

docker pull dpage/pgadmin4
docker run -p 5050:80 -e &quotPGADMIN_DEFAULT_EMAIL=mortezadalil@gmail.com&quot -e &quotPGADMIN_DEFAULT_PASSWORD=1234567&quot -d

در مرورگر آدرس لوکال هاست با پورت 5050 را مینویسیم تا به محیط مدیریت متصل شویم. یوزرنیم و پسورد pgAdmin را وارد میکنیم و صفحه زیر را خواهیم دید :

روی Add new server کلیک میکنیم تا پنجره زیر رویت شود.

انتخاب یک نام دلخواه برای کانکشن
انتخاب یک نام دلخواه برای کانکشن

اسم برای این کانکشن به نام pricedb انتخاب میکنیم و به تب بعدی میرویم.

عدم دسترسی به localhost:5432
عدم دسترسی به localhost:5432

با وارد کردن اطلاعات فوق خطایی که میبینید مشاهده میکنیم.

واضح است که ما از روی سیستم خودمان و به کمک Visual Code میتوانیم به دیتابیس Postgres متصل شویم ولی از طریق محیط PGAdmin نمیتوانیم چون این محیط در یک کانتینر دیگر قرار دارد و به آدرس localhost:5432 دسترسی ندارد. حتا اگر اسم کانتینر دیتابس هم وارد کنیم به آن دسترسی ندارد چون در یک شبکه نیستند.

حتا با وارد کردن اسم کانتینر هم به آن دسترسی نداریم.
حتا با وارد کردن اسم کانتینر هم به آن دسترسی نداریم.

باید این دو را در یک شبکه قرار دهیم این موضوع را در مقالات قبلی ررسی کردیم. (در این مقاله در بخش ساخت فایل داکر)

کانتینرها را پاک میکنیم.

اولش پاک نکرد چون باید ابتدا استاپ میکردیم ولی ما از -f برای حذف زورکی کانتینر استفاده کردیم!
اولش پاک نکرد چون باید ابتدا استاپ میکردیم ولی ما از -f برای حذف زورکی کانتینر استفاده کردیم!
دستورات را طوری مینویسیم که در یک شبکه واحد باشند
docker network create node-postgres-network
docker run --name mypostgres --network node-postgres-network -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=123456 -p 5432:5432 -d postgres
docker run -p 5050:80 --network node-postgres-network -e &quotPGADMIN_DEFAULT_EMAIL=mortezadalil@gmail.com&quot -e &quotPGADMIN_DEFAULT_PASSWORD=1234567&quot -d dpage/pgadmin4

حالا در مرورگر localhost:5050 را صدا میزنیم و یوزر پسورد PGAdmin را وارد میکنیم مراحل قبل را تکرار میکنیم و اینبار ارتباط برقرار خواهد شد.

دقت کنید فعلا قصد develop داریم و اجرای کانتینرهای مرتبط با هم را به شکل دستی با خود داکر هندل کردیم ولی حتما میدانید که میتوانستیم یک فایل docker-compose برای ارتباط این دو کانتینر بنویسیم و این مسیر طولانی را طی نکنیم. در انتها مثل مقالات قبلی به کمک docker-compose پابلیش میکروسرویس ها و دیتابیس ها را هندل خواهیم کرد.

حالا دیتابیس مورد نظر را میسازیم:

حالا تیبل مورد نظر را میسازیم:

نام را در تب جنرال برای تیبل به نام Price انتخاب کردیم و در تب دوم به شکل زیر ستون ها و نوعشان را مشخص میکنیم و در نهایت دکمه Save را میزنیم.

میتوانیم به شکل زیر هم تیبل را بسازیم :

دو ردیف دیتا برای یک کالای خیالی ایجاد میکنیم:

یعنی قبلا قیمت کالا 1000 بوده و بعدا به 1500 تغییر کرده.

در بخش کوئری کالای شماره 1 را سرچ کرده و به ترتیب تاریخ نزولی مرتب میکنیم و اولین نتیجه را که همان آخرین تغییر قیمت است بیرون میکشیم:

این کوئری را به خاطر داشته باشید که بعدا با آن کار داریم.

قبل از ایجاد پروژه یادآوری میکنیم که به کمک اکستنشنی که پیش از این برای ویژوال کد معرفی کردم میتوانید به تیبل ایجاد شده دسترسی داشته باشید:

تولید api برای کار با میکروسرویس قیمت و تخفیفات

1. مراحل ایجاد پروژه را بر اساس مقاله قبل انجام دهید و یک پروژه به نام Discount.api بسازید. (مراحل 1 تا 4)

2. مدلی که قرار است این میکروسرویس با آن کار کند به شکل زیر ایجاد میکنیم.

https://gist.github.com/2d488c059c8e3fefad073376c1b0c818

3. حالا باید تصمیم بگیریم چه api هایی قرار است ایجاد کنیم. یک تیبل ساده ایجاد کردیم که به ازای هر کالا در تاریخ های مختلف قیمت (Amount) و قیمت بعد از تخفیف(CurrentAmount) را در خود دارد. طبیعتا این حالت ساده در یک پروژه واقعی جوابگوی همه نیازها نیست ولی برای اهداف آموزشی مدل رضایت بخشیست! مثلا ممکن است قیمت کالا برای یک کاربر خاص تخفیف داشته باشد یا بهتر باشد تاریخچه تغییر قیمت را دقیق تر داشته باشیم که این موارد بیزنس متفاوتی میطلبد.

آنچه برای این میکروسرویس نیاز داریم به شکل زیر است:

  • متد GetPrices که قیمتهای مربوط به یک کالا را برمیگرداند.
  • متد CreatePrice
  • متد UpdatePrice
  • متد DeletePrice
در مقاله قبل هم گفته شد که قرار نیست تمامی قواعد Clean Architecture را رعایت کنیم و این میکروسرویس صرفا جنبه آموزشی به لحاظ قرارگیری در یک مجموعه میکروسرویس ها را دارد. در ادامه این api ها را ایجاد میکنیم.

4. میخواهیم از Dapper به عنوان یک ORM درست و حسابی استفاده کنیم. از خوبیهای Dapper همینقدر کافیست که بدانیم در سایت StackOverflow استفاده شده. برای اطلاعات بیشتر این ویدیو را ببینید.

Entity Framework Community Standup - Dapper

برای کار با دپر ایجاد ریپازیتوری و متدهای جنریک از پیش تعریف شده به منظور Pagination و Sort و Filter توصیه میشود. متاسفانه در این مقاله مجال پرداختن به این مسائل را نداریم.

دپر را به شکل زیر نصب میکنیم

dotnet add package Dapper

همچنین برای دسترسی دات نت به PostgreSQL کتابخانه زیر را نصب میکنیم

dotnet add package Npgsql

بعد از نصب فایل csproj به شکل زیر خواهد شد:

5. حالا کانکشن استرینگ را مشخص میکنیم :

6. کنترلری به نام DiscountController میسازیم و شروع به پیاده سازی متدها میکنیم.

7. متد GetPrices را طوری مینویسیم که شناسه کالا را بگیرد و کل قیمت های آن را به شکل نزولی بدهد.

برای تست به کمک dotnet run در ترمینال، پروژه را اجرا کرده و از Swagger به جای Postman استفاده میکنیم.

سوئگر ابزاریست که میتوانیم به کمک آن api های تولید شده به همراه ورودی و خروجی های آن را ببینیم و تست کنیم.
به طور پیش فرض این کتابخانه در پروژه webapi دات نت کور وجود دارد.

کافیست بعد از اجرا در مرورگر عبارت زیر را بنویسیم :

https://localhost:7256/swagger/index.html

8. متد مربوط به ایجاد و آپدیت و حذف رکورد قیمت را مینویسیم تا کنترلر کامل شود.

https://gist.github.com/201c10701bb23961d9c0560bc295d88c

درباره gRPC


آنچه تا اینجا دیدیم تقریبا چیزی شبیه به مقاله قبل یعنی CRUD مربوط به سبد خرید بود. در حقیقت تنها دیتابیس و ORM ما متفاوت بود. اما به شکل ارتباط بین میکروسرویس ها در ابتدای مقاله دقت کنید. میکروسرویس Basket وظیفه دارد اطلاعات قیمتی مربوط به کالاهای موجود در سبد را از میکروسرویس Discount استخراج کند. این ارتباط داخلی و بین دو میکروسرویس است. با چیزی که تا الان نوشتیم متد GetPrices از بیرون قابل استفاده است و با یک Http Request چه از طرف مرورگر و چه از طرف یک ماژول دیگر میتواند پاسخ مناسب را برگرداند، اما برای ارتباط داخلی تا اندازه ای کند است یا بهتر بگوییم تکنولوژی دیگری وجود دارد که با سرعت بهتری پاسخ را برمیگرداند.

از تکنولوژی grpc زمانی که نیاز به پاسخ سریع برای ادامه عملیات باشد برای ارتباط بین میکروسرویس با میکروسرویس دیگر در Back-end استفاده میشود.

جی آر پی سی قابلیت پشتیبانی از برنامه نویسی mixed دارد مثلا یک طرف python و دیگری سی شارپ باشد.

توان عملیاتی بالا و پاسخ سریع از ویژگیهای ارتباط gRPC است.

میتوان برای ارتباط point to point از grpc استفاده کرد تا مسیج ها را به شکل Realtime ارسال نمود.

حجم کمتر اطلاعات ارسالی به نسبت json در این روش از مزیت های مهم gRPC است.

به کمک gRPC میتوان الگوهای محاسباتی را بین چندین میکروسرویس پیاده سازی کرد و در نهایت خروجی را به سمت کلاینت ارسال نمود.

تنظیمات مربوط به gRPC باید در دو سوی آن انجام شود. یعنی هم میکروسرویس Basket به عنوان مصرف کننده (Consumer or Client) و هم میکروسرویس Discount به عنوان خدمات دهنده (Server) کتابخانه های مربوط و تنظیمات مرتبط را داشته باشند.

برای برقراری ارتباط gRPC نیاز به استفاده از پروتکل http/2 است.

ما در ادامه بخش Server یک ارتباط gRPC را پیاده خواهیم کرد.
تا اینجای کار یک CRUD ساده به کمک Rest API را پیاده سازی کرده ایم.
به دلایلی که گفته شد میخواهیم متد GetPrices را به کمک gRPC هم پیاده سازی کنیم.


پیاده سازی بخش Server یک ارتباط gRPC

1. اگر بخواهیم داخل پروژه Api خود سرویس هایی به عنوان Server وبا ارتباط gRPC بنویسیم، باید ابتدا باید دستور زیر را برای اضافه شدن کتابخانه مورد نیاز استفاده کنید(ممکن است در ارتباط برقرار کردن با آدرس های گوگل به مشکل بخورید پس حتما از VPN استفاده کنید یا به کمک شکن dns خود را عوض کنید)

dotnet add package Grpc.AspNetCore --version 2.44.0

بعد از نصب فایل csproj شما به شکل زیر خواهد شد.

2. به فایل csprojکد زیر را اضافه میکنیم:

این یعنی ما سرویسی به عنوان خدمات دهنده (Server) ایجاد کرده ایم که فایل Proto آن در مسیر Protos\greet.proto است. این فایل فرمت ارتباطی را مشخص میکند. یعنی اینکه چه چیزی با چه ورودی و خروجی صدا زده خواهد شد.
این یعنی ما سرویسی به عنوان خدمات دهنده (Server) ایجاد کرده ایم که فایل Proto آن در مسیر Protos\greet.proto است. این فایل فرمت ارتباطی را مشخص میکند. یعنی اینکه چه چیزی با چه ورودی و خروجی صدا زده خواهد شد.


3.حالا این فایل proto را میسازیم. قبل از هر چیز برای اینکه ویژوال کد این فایل را بشناسد اکشتنشن زیر را نصب کنید.

یک فولدر به نام Protos میسازیم و فایل زیر را درون آن ایجاد میکنیم:

https://gist.github.com/2faa6269f9e29bd2324c63f564dcf198

جی آر پی سی از این فایل قراردادی برای توسعه API مرتبط استفاده میکند. این پروتکل را Protobuf می نامند(Protocol Buffer). این کدها قراردادی بین زبان های برنامه نویسی متفاوت هستند. این فایل شامل دو چیز است :

  • تعریف سرویس gRPC
  • پیامی که بین کلاینت ها و سرور ها ارسال میشود.

در مثالی که میبینید ما سرویسی به نام Greeter تعریف کردیم که درون آن متدی به نام SayHello وجود دارد. این متد یک ریکوئست از نوع HelloRequest میفرستد که شامل یک پراپرتی از نوع استرینگ به نام name است و در پاسخ یک ریسپانس از نوع HelloReply برمیگرداند که یک پراپرتی از نوع استرینگ به نام message دارد.

کافیست پروژه را با دستور dotnet build کامپایل کنید تا dll آن ساخته شود.

در مسیر obj/debug/net6.0/protos از روی فایل proto فایل های cs درست میشود که نباید در آنها تغییری ایجاد کنید چون همانطور که در تصویر زیر میبینید در بالای آن اشاره شده که این فایل هر بار در حین کامپایل به شکل خودکار ایجاد میشود و ویرایش آن بی‌فایده است.

در این فولدر دو فایل وجود دارد:

  • فایل Greet.cs که شامل همه پروتکل هاییست که برای انتشار یا سریالایز کردن ریکوئست و ریسپانس لازم است
  • فایل GreetGrpc.cs که کلاس های کلاینت و سرور را ایجاد میکند و شامل :
    • یک کلاس ابسترکت به نام Greeter.GreeterBase است که سرویسی که قرار است بنویسیم از این کلاس باید ارث بری کند.
    • یک کلاس به نام Greeter.GreeterClient که برای دسترسی نمونه های کلاس Greeter کاربرد دارد.
حتما هر بار که فایل Proto را تغییر دادید و سپس پروژه را بیلد کردید و فایل جدید cs از روی Proto در محل گفته شده ایجاد شد، یکبار Vs Code را ببندید و باز کنید یا از Reload Window به کمک F1 استفاده کنید. (F1 را فشار دهید و گزینه Reload Window را انتخاب کنید)
حتا گاهی اوقات وقتی VsCode قاطی کرد از این روش برای Reload کردن صفحه استفاده کنید. یعنی دکمه F1 و سپش انتخاب Realod Window
حتا گاهی اوقات وقتی VsCode قاطی کرد از این روش برای Reload کردن صفحه استفاده کنید. یعنی دکمه F1 و سپش انتخاب Realod Window


اگر اینکار را نکنید نمیتوانید از کلاس های جنریت شده در پروژه خود استفاده کنید.

4. کلاس موجود در سرویس زیر را از روی فایل جنریت شده ارث بری کرده و متد SayHello را پیاده سازی کردیم که ریسپانس تولید کند. (اگر مرحله Reload Window را انجام ندهید کادر قرمز رنگ Greeter.GreeterBase برای Visual Code شناسایی نمیشود و نمیتوانید از آن ارث بری کنید)

https://gist.github.com/266908e68d2b48e4350dde8a5836a3b2

5. این سرویس باید برای دات نت شناسانده شود پس در فایل Startup یا Program کد زیر را اضافه کنید


6. چون پروژه ما api بر اساس Rest دارد باید قابلیت ارتباط با Http1و Http2 را داشته باشد. به همین منظور تنظیمات Kestrel را به appsettings.json اضافه میکنیم.

https://gist.github.com/a2f3aa486486bb34e3a56c518887dc6f

7. برای تست سرویس gRPC، از یک کلاینت مرسوم استفاده کنید. مثلا BloomRpc داری GUI و بسیار ساده و کار راه انداز است.

پروژه را اجرا کنید بر اساس تنظیماتی که در appsettings در شماره 6 اضافه کردیم حالت Http2 روی پورت 5003 اجرا میشود. پس آدرس را در BloomRpc روی localhost:5003 قرار میدهیم و در سمت چپ دکمه مثبت را فشرده و آدرس فایل Proto را انتخاب میکنیم و در نهایت دکمه سبز رنگ Play در وسط صفحه را میزنیم تا پاسخ را ببینیم.

8. حالا که همه چیز به خوبی کار کرد سرویس اصلی برای برگرداندن قیمت کالا را مینویسیم. 7 قدم فوق را به دقت برای سرویس جدید رعایت میکنیم:

  • ابتدا فایل price.proto را درست میکنیم. که حاوی یک سرویس به نام Price با متد GetPrice است که ورودی آن شناسه کالا و خروجی آن آخرین قیمت است:
https://gist.github.com/9d2ffae90908cf85bdd5fd5a7af0565a


  • این فایل را در csproj مینویسیم.
https://gist.github.com/893bd0ae00fbc3188ec674bc77c49982
  • پروژه را بیلد کرده و Vs Code را Reload میکنیم.
  • سرویس مرتبط با این فایل proto را میسازیم.
https://gist.github.com/d3d55092e4784bbc778cac68f103d12a
  • این سرویس را به فایل program یا startup اضافه میکنیم.

تست با BloomRpc


پیاده سازی سرویس کلاینت برای استفاده از gRPC Server

1. پروژه ای که قرار است از اطلاعات سرویس قیمت استفاده کند Basket.Api است. در Vs Code این پروژه را باز میکنیم. سرویسی که باید اصلاح شود UpdateBasketItems است، یعنی اطلاعاتی از کالا در سبد ذخیره میشود که کاملا درست باشد. پس قیمت یا تخفیف از UI دریافت نمیشود. کالاها و تعدادشان از UI دریافت شده و به کمک gRPC اطلاعات قیمتی کالاها از میکروسرویس Discount.Api گرفته میشود و در سبد ذخیره میشود (سبد در Redis نگهداری میشود. مقاله قبل را ببینید.)

2. کتابخانه های زیر را به میکروسرویس Basket.Apiاضافه میکنیم.

dotnet add package Grpc.Net.Client
dotnet add package Grpc.Tools
dotnet add package Google.Protobuf

3. بخش زیر را به csproj اضافه میکنیم. در حقیقت به جای انتقال فایل proto به پروژه Discount به همان فایلی که ساختیم رفرنس میدهیم که اگر بعدا دستخوش تغییر شد نیازی به تغییر فایل جدید در پروژه دیگر نباشد.

اینکار باعث بروز مشکلی در داکر میشود. با اجرا docker build مسیر گفته شده قابل شناسایی نخواهد بود، یعنی داکر نمیتواند خارج از کانتکستی که dockerfile در آن قرار دارد کاری کند، پس Parent Directory برای آن بی معنیست و خطای زیر را خواهیم دید.

برای جلوگیری از این مشکل به ساده ترین شکل میتوانید فایل های Proto را در یک Git Repository جدا قراردهید و در دل هر ریپازیتوری که به آن نیاز بود آنها را pull کنید. با این روش هر تغییر روی Proto ها در تمامی پروژه هایی که با آن کار میکنند اعمال خواهد شد.

خوشحال میشم اگر راه حل بهتری دارید برای من کامنت بذارید، من تگ هایی رو در csproj دیدم که حوزه کار docker رو تغییر میدادند ولی متاسفانه ازشون سر در نیاوردم. خلاصه اینکه مشتاقم نظرات و راه حل هاتون رو بدونم.

در این مثال ما فولدر Proto را از پروژه Discount.Api به Basket.Api کپی کردیم.(گیت ریپازیتوری نساختیم و صرفا کپی کردیم) و با اینکار ساختار فایل csproj مربوط به Basket.Api به این شکل خواهد شد.

4. پروژه را بیلد و Vs Code را Reload میکنیم و بررسی میکنیم که در فولدر Obj/Debug فایل price.cs و priceGrpc.cs ساخته شده باشند.

5. در appsettings یک آیتم برای ذکر آدرس gRPC Server اضافه میکنیم.

6. متد آپدیت سبد به شکل زیر اصلاح میشود

https://gist.github.com/d9c48acc45e1c4a354fea955dbbc6bc7

واضح است که ابتدا آدرس gRPC Server را فراخوانی کرده و یک آبجکت clientمیسازیم.

سپس روی کالاهای پیمایش انجام داده و اگر قیمت برای آن از gRPC Server دریافت کردیم آن را آپدیت میکنیم.

یادآوری میکنم که هندل کردن اکسپشن در یک پروژه واقعی به شکل فوق انجام نمیشود و بهتر است از یک MiddleWare برای هندل کردن تمامی اکسپشن ها استفاده کرد و یک فرمت مشخص و یکسان در خروجی داشته باشیم. همچنین صدا زدن سرویس gRPC معمولا در اکشنهای کنترلر انجام نمیشود و سرویس ها(یا هندلرها( که معمولا روال بیزنسی یک فرایند را برعهده دارند نیاز به صدا زدن gRPC Server دارند. در این مجموعه مقالات به خاطر جلوگیری از پیچیدگی از Repository و Service (handler) و ... استفاده نکردیم و تمام کارها را در کنترلر انجام دادیم.

و تمام!

نکاتی در مورد داکرایز کردن پروژه

1. طبق معمول dockerfile و dockerignore را در مسیر اصلی پروژه میسازیم. میتوانیم از میکروسرویس Basket.Api کمک بگیریم چون آن پروژه هم دات نتی بود.

دقت کنید که دو پورت باید به بیرون expose شود. 5001 و 5003 که اولی بر اساس http1و دومی بر اساس http2 کار میکند.

2. دو فایل docker-compose.yml و docker-compose.override.yml را بر اساس کد زیر تغییر میدهیم. سه ایمیج جدید به این فایل ها اضافه شده، یکی مربوط به PostgreSQL و دومی هم پروژه Discount.Api است و دیگری هم مدیریت دیتابیس Postgre به نام PgAdmin است.

3. فراموش نکنید که appsetting مربوط به پروژه Basket.Api به خاطر دسترسی به gRPC Server دستکاری شد. یعنی آیتم DiscountGrpcServerUrl هم باید از localhost خارج شود و به Container مرتبط با میکروسرویس Discount.Api اشاره کند.

با اینکار یک اتفاق بد رخ میدهد. وقتی پروژه Basket.Api را به منظور تست به صورت لوکال اجرا کنید و از طرفی پروژه Discount.Api هم اجرا کنید تا ارتباط gRPC را تست کنید، مجبورید این خط را تغییر دهید تا به localhost وصل شود. بهتر است. این خط را به شکل لوکالی باقی بگذارید و کانکشن استرینگ را در فایل داکر کامپوز override کنید.

4. فراموش نکنید که تنظیمات appsettings را به یکی از روش های گفته شده در مقاله قبل اصلاح کنید یا به شکلی که در نکته قبلی گفته شد در فایل docker-compose محتوای appsettings را override کنید. آدرس ها نباید مستقیم باشد و باید با صدا زدن نام Container به میکروسرویس های دیگر دسترسی داشته باشیم. مثلا در کانکشن استرینگ مرتبط با PostgreSQL در میکروسرویس Discount.Api باید به جای localhost از نام کانتینر حاوی این دیتابیس استفاده کرد. (postgresdb)

	مثل نکته شماره 3 به کانکشن استرینگ داخل اپ ستینگ دست نزدیم و همان را به شکل اصلاح شده در docker-compose آوردیم.
مثل نکته شماره 3 به کانکشن استرینگ داخل اپ ستینگ دست نزدیم و همان را به شکل اصلاح شده در docker-compose آوردیم.


5. برای اینکه به سوئگر در حالت داکر شده دسترسی داشته باشید تایپ داکر را در فایل داکر Development بگذارید (یا در داکر کامپوز)

تعیین محیط در dockerfile که در سی شارپ میتوانید با استفاده از دستورات مرتبط با environment مقدار آن را بیرون کشیده و تصمیم درست بگیرد. در این حالت از appsettings-development استفاده میکند.
تعیین محیط در dockerfile که در سی شارپ میتوانید با استفاده از دستورات مرتبط با environment مقدار آن را بیرون کشیده و تصمیم درست بگیرد. در این حالت از appsettings-development استفاده میکند.


تعیین محیط در docker-compose
تعیین محیط در docker-compose
حتما نکته سوم در انتهای مقاله قبل را به دقت بخوانید. استفاده از Environment Variable ها از مطمئن ترین روش ها برای ذخیره مقادیر حساس است. دسترسی به این متغیرها در نکته سوم همان مقاله توضیح داده شده است.

فایل های docker-compose در نهایت به شکل زیر خواهند شد.

https://gist.github.com/729c169aa0eba85b88bdfa0d237b6580


https://gist.github.com/a0102b9a1ef005b489e892232691f71d




مراحل تست ارتباط gRPC پس از اجرای docker-compose

با دستور زیر مجموعه میکروسرویس هایی که در docker-compose کانفیگ شدند را اجرا میکنیم

docker-compose -f .\docker-compose.yml -f .\docker-compose.override.yml up -d --force-recreate

در صورت نیاز با این دستور سرویس ها را متوقف میکنیم

docker-compose down
دقت کنید برای تست سرویس های داکرایز شده نیاز دارید که تسلط نسبی روی ایجاد و حذف و اجرای ایمیج ها داشته باشید. همچنین باید بتوانید به کمک exec و ورود به ترمینال کانتینر دستوراتی را به منظور تست بنویسید.

به محیط pgAdmin وارد شوید (از روی فایل داکر کامپوز مشخص است که به پورت 5050 مپ شده پس دستور زیر را در مرورگر مینویسیم و لاگین میکنیم)

http://localhost:5050/

ابتدا چک کنیم که ارتباط pgAdmin با PostgreSQL برقرار باشد.

حالا دیتابیس و تیبل را میسازیم:

به کمک Swagger یک رکورد به این تیبل اضافه میکنیم( کانتینر ما به سوئگر دسترسی دارد چون نوع Environment آن را Development تعیین کردیم و سوئگر در این حالت کار میکند)

تا اینجای کار میکروسرویس جدید ما درست کار میکند، یعنی پورت Http1 آن که برای Rest Api است به درستی عمل کرد، همچنین ارتباط آن با دیتابیس برقرار است.

با یکبار حذف کانتینر و راه اندازی مجدد docker-compose میتوانیم ببینیم که Volume آن نیز درست کار میکند. یعنی دیتابیس ساخته شده و تیبل ایجاد شده و محتوای آن حفظ میشوند چون دیتابیس را از کانتینر خارج کرده ایم.

حالا به کمک پستمن متد UpdateBasketItems را تست میکنیم. (در میکروسرویس Basket.Api به سوئگر آن دسترسی نداریم چون در حالت Development داکر آن اجرا نشده است)

همه چیز درست کار کرد و قیمت کالا با شناسه 1 به جای 55000 که از ورودی گرفته شده بود مقدار 10 در سبد ثبت شده است.


وقتی در ارتباط gRPC به مشکل خوردیم چه کنیم؟

بهتر است سوال را جامع تر مطرح کنیم. اگر در ارتباط بین دو میکروسرویس به خطا خوردیم چه کار کنیم؟ شاید ارتباط Http1 و از نوع Rest Api یا Http2 و از نوع gRPC باشد. در مقاله قبل در بخش «ساخت داکر» شماره 3 به همین مورد اشاره شد و با ورود به ترمینال Container و نصب Curl و استفاده از آن برای ارتباط برقرار کردن با دیگر میکروسرویس ها توانستیم عیب یابی کنیم.

در ایجا نیز باید شبیه همان روش عمل کنید با این تفاوت که از apt-get برای نصب خبری نیست چون ابزار تست gRPC یعنی grpcurl باید مستقیم از git دانلود شود.

docker exec -it basketapi /bin/bash
apt-get update
apt-get install wget
wget https://github.com/fullstorydev/grpcurl/releases/download/v1.1.0/grpcurl_1.1.0_linux_x86_64.tar.gz
tar -xvzf grpcurl_1.1.0_linux_x86_64.tar.gz
chmod +x grpcurl
mv grpcurl /usr/local/bin/grpcurl
grpcurl --help
grpcurl discountapi:5003 describe

ابتدا وارد ترمینال کانتینر basketapi میشویم.
آپدیت apt-get را انجام میدهیم.
ابزار wgetرا به منظور دریافت فایل از لینک مستقیم اینترنتی نصب میکنیم.
از آدرس فایل را با wgetمیگیریم.
فایل tar را باز میکنیم.
دسترسی آن را به حالت اجرایی تبدیل میکنیم.
فایل را به فولدر دیگری منتقل میکنیم که از همه جا در دسترس باشد.
آپشن help آن را اجرا میکنیم تا از وجود و همچنین آپشن های این فایل مطمئن شویم.
حالا از آپشن describe برای درک وضعیت ارتباطی میکروسرویس خود با میکروسرویس discountapi استفاده میکنیم. اگر سرویس در اینجا قطع باشد طبیعتا مشکل از کد است و بهتر است به شکل لوکالی کدهای خود را دیباگ کنید. اگر سرویس در اینجا وصل باشد به پورت های خروجی از داکر و تعریف پورت ها در appsetting میکروسرویس Discount.Api دقت کنید.

کدهای مربوط به این بخش را در این ریپازیتوری از گیتهاب ببنید (کامیت پایانی تا اینجا "پایان میکروسرویس قیمت و تخفیفات")


برنامه نویسیآموزش microserviceآموزش میکروسرویسآموزش grpcپیاده سازی
برنامه نویس و علاقمند به برنامه نویسی، سینما، فلسفه و هر چیزی که هیجان انگیز باشد. در ویرگول از روزمرگیهای مرتبط با علاقمندیهام خواهم نوشت. در توئیتر و جاهای دیگر @mortezadalil هستم.
شاید از این پست‌ها خوشتان بیاید