فراخوانی سرویس-به-سرویس با Dapr و .NET در مایکروسرویسها
چیزایی در مورد مایکروسرویس ها یا Dapr شنیدید و می خاید بیشتر بدونید؟ شاید بخاید برای شروع چند کد هم یاد بگیرید. من می تونم بهتون کمک کنم.
مایکروسرویس: یک سبک معماریه که یک نرما فزار رو به صورت مجموعه ای از سرویسها , پیاده سازی می کنه.
- بسیار قابل نگهداری و آزمایش پذیر
- عدم وابستگی
- به طور مستقل deploy کردن
- سازماندهی شده حول قابلیت های بیزینس
میکروسرویس راهی برای پیاده سازی سیستم توزیع شدست. همه میکروسرویس ها سیستم های توزیع شده هستند اما همه سیستم های توزیع شده میکروسرویس نیستند
اما چرا من به چنین معماری نیاز دارم؟ هر چه افراد بیشتری روی یک واحد قابل استقرار(ساب سیستم یا ماژول) کار کنند، کارایی آن کمتر می شود. Microservice در این مورد کمک می کند، جایی که تیم های کوچکتر می توانند روی یک واحد قابل استقرار کار کنند. این واحدها ممکن است تحت تأثیر واحدهای دیگر قرار بگیرند یا نگیرند. آنها از راههای مختلف با یکدیگر ارتباط برقرار می کنند. اینجاست که Dapr وارد بازی میشه.
Dapr:- Dapr بهترین شیوهها برای ساخت برنامههای میکروسرویس را در قالب APIهای باز و مستقل به نام بلوکهای ساختمانی ,میباشد که به شما امکان میده برنامههای قابل حمل را با زبان و فریم ورک انتخابی خود بسازید. برای مثال، یک میکروسرویس در پایتون نوشته شده ، در حالی که دیگری در دات نت نوشته شده. آنها همچنین می توانند به راحتی با استفاده از Dapr ارتباط برقرار کنند.
Dapr بسیاری از عملکردهای ضروری را برای معماری میکروسرویس فراهم می کند.
در این مقاله، ابتدا فراخوانی سرویس به سرویس را مورد بحث قرار خواهم میدیم.
- سرویس A با استفاده از HTTP یا gRPC سرویس B رو کال می کنه. تماس به کالسکه Dapr(sidecar Dapr) محلی میره.
- Dapr مکان سرویس B را با استفاده از مولفه تفکیک نام(Name Resolution Component) که در پلت فرم میزبانی داده اجرا می شود، پیدا می کنه.
- Dapr , پیغام رو به Dapr سرویس B میفرسته (معمولن با استفاده از gRPC)
- Dapr سرویس B , درخواست را به endpoint (یا متد) مشخص شده در سرویس B ارسال می کنه. سپس سرویس B کد لاجیک بیزینش خودش رو اجرا می کنه.
- سرویس B پاسخی را به سرویس A ارسال می کنه.پیغام به دست کالسکه سرویسB میرسه.
- Dapr پاسخ رو به کالسکه Dapr سرویس A ارسال می کنه.
- سرویس A پاسخ را دریافت می کند.
چند کلمه جدید مانند کالسکه(sidecar) وجود داره. بیایید تک تک آنها را درک کنیم.
(کالسکه)Sidecar:
در تصویر بالا یک کالسکه کناری به دوچرخه وصل شده است. به طور منطقی، ارائه دهنده قابلیت های برنامه توزیع شده Dapr , به سرویس ما متصل است. به همین خاطر بهش میگم کالسکه(sidecar). APIهای Dapr بر روی یک فرآیند جداگانه (یعنی Dapr sidecar) اجرا می شوند و در کنار برنامه شما اجرا می شوند. فرآیند داپر سایدکار daprd نام دارد.
کامپوننت Name Resolution: - این کامپوننت به یافتن سایدکار(کالسکه) سرویس مورد نظر کمک می کند. Dapr این امکان را برای کاربران فراهم می کنه که با سایر برنامه هایی که دارای شناسه منحصر به فرد هستند تماس بگیرند. این قابلیت به برنامهها اجازه میده تا از طریق شناسههای نامگذاری شده با یکدیگر تعامل داشته باشن و بار کشف سرویس را بر عهده runtime Dapr قرار میدهد.
رمزگذاری mTLS: - امنیت لایه حمل و نقل متقابل (mTLS) فرآیندی است که یک اتصال TLS رمزگذاری شده ایجاد می کنه که در آن هر دو طرف از گواهی های دیجیتال (X.509) برای احراز هویت یکدیگر استفاده می کنند. من عمیقاً در مورد اینکه چرا به این نیاز داریم نمی پردازم. اگر علاقه مند هستید، می تونید به این مقاله ویکی پدیا نگاهی بیندازید.
💡 در یک سیستم توزیعشده، معماری Dapr sidecar به مکانیابی سرویسهای دیگر، فراخوانی ایمن سایر سرویسها و مدیریت تلاشهای مجدد در صورت وقفههای موقت سرویس کمک میکند.
من دو میکروسرویس (Web API) با نامهای ServiceOne و ServiceTwo ایجاد میکنم.
دو بسته نوگت Dapr.Client و Dapr.AspNetCore مورد نیاز است.
بیایید اکنون پروژه ServiceOne را با Dapr ایجاد کنیم.
- ساختار پوشه:
(شما می توانید ساختار پوشه را همانطور که ترجیح می دهید نگه دارید.)
- Startup.cs:
Dapr را اضافه کنید
- IDaprClientHelper:
در این interface ، متدی به نام ResponseByDaprClient تعریف کردیم. پیاده سازی این متد قراره مرحله ی فراخوانی سایر میکروسرویس ها با استفاده از sidecar باشه.
using System.Net.Http;
using System.Threading.Tasks;
namespace ServiceOne
{
public interface IDaprClientHelper
{
Task<T> ResponseByDaprClient<T>(HttpMethod httpMethod,string appId,string endPoint);
}
}
- DaprClientHelper:
متد ResponseByDaprClient مفاهیم بسیار زیادی داره. بیاید بیشتر باهاشون آشنا شیم.
using Dapr.Client;
using System.Net.Http;
using System.Threading.Tasks;
namespace ServiceOne
{
public class DaprClientHelper : IDaprClientHelper
{
public async Task<T> ResponseByDaprClient<T>(HttpMethod httpMethod, string appId, string endPoint)
{
var daprClient=new DaprClientBuilder().Build();
HttpRequestMessage daprRequest= daprClient.CreateInvokeMethodRequest(httpMethod,appId, endPoint);
var result=await daprClient.InvokeMethodAsync<T>(daprRequest);
return result;
}
}
}
4.1 httpMethod: هر API یک روش درخواست HTTP منحصر به فردی داره . مثل PATCH/POST/GET/PUT/DELETE. httpMethod حاوی جزئیات روش درخواست HTTP است.
4.2 appId: مؤلفه Resolution Name که از پارامتر appId برای یافتن و فراخوانی یک سرویس در آن سرویس خاص استفاده می کنیم. AppId در docker-compose.yaml ذکر خواهد شد. بعداً در این مقاله به آن می پردازیم.
4.3 endPoint: مسیر نسبی کنترلر API.
4.4 daprClient: پکیج Dapr.client امکان تعامل با سایر برنامه های Dapr را از یک برنامه دات نتی می ده. daprClient نمونه ای از DaprClientBuilder است که برای فراخوانی سایر سرویس های dapr استفاده می شود.
4.5 daprRequest: یک HttpRequestMessage که می تونه برای فراخوانی سرویس در برنامه مشخص شده توسط appId استفاده بشه و متد مشخص شده توسط methodName(endPoint) را با روش HTTP مشخص شده توسط httpMethod فراخوانی کنه.
4.6 InvokeMethodAsync: فراخوانی سرویس را با استفاده از درخواست ارائه شده توسط درخواست انجام دهید. اگه پاسخ دارای کد وضعیت موفق باشه، بدنه با استفاده از JSON به مقداری از نوع T از سریال خارج می شه. در غیر این صورت یک استثنا ایجاد می شه.
- IServiceTwoHelper:
using System.Threading.Tasks;
namespace ServiceOne
{
public interface IServiceTwoHelper
{
Task<string> GetMessage();
}
}
در این interface ، متدی به نام GetMessage تعریف کردیم. پیاده سازی این متد, شامل مراحل فراخوانی API ServiceTwo به نام GetMessage است.
using Microsoft.AspNetCore.Mvc;
namespace ServiceTwo.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet("[action]")]
public ActionResult<string> GetMessage()
{
return new JsonResult("contact with serviceTwo");
}
}
}
- ServiceTwoHelper:
IDaprClientHelper را به این کلاس اینجکت کردیم، پس می تونیم از متد ResponseByDaprClient برای ارتباط ServiceOne به ServiceTwo استفاده کنیم. ResponseByDaprClient سه پارامتر را به عنوان ورودی می گیره.
using System.Net.Http;
using System.Threading.Tasks;
namespace ServiceOne
{
public class ServiceTwoHelper : IServiceTwoHelper
{
IDaprClientHelper _clientHelper;
public ServiceTwoHelper(IDaprClientHelper clientHelper)
{
_clientHelper = clientHelper;
}
public async Task<string> GetMessage()
{
var response=await _clientHelper.ResponseByDaprClient<string>(HttpMethod.Get,"servicetwoapp", "/api/Values/GetMessage");
return response;
}
}
}
مقدار httpMethod برابر با HttpMethod.Get هست چون ServiceTwo API یک متد Get است.
مقدار appId برابر است با "servicetwoapp"، این نام در فایل docker-compose ذکر خواهد شد.
مقدار endpoint برابر است با "/Home/GetMessage"، آدرس API ServiceTwo است.
- HomeController:
using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
namespace ServiceOne.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class HomeController : ControllerBase
{
IServiceTwoHelper _serviceTwo;
public HomeController(IServiceTwoHelper serviceTwo)
{
_serviceTwo = serviceTwo;
}
public async Task<ActionResult<string>> Index()
{
var response = await _serviceTwo.GetMessage();
return new JsonResult($"response from ServiceTwo {response}");
}
}
}
حالا فایل docker-compose.yaml.
image: daprd فقط نام فرآیند داپر سایدکار است. بسیاری از تصاویر Docker منتشر شده برای هر یک از اجزای Dapr موجود در Docker Hub وجود دارد. می توانید از daprd:latest برای آخرین نسخه استفاده کنید. همیشه باید از نسخه ثابت و پایدار برای محیط تولید استفاده کنید.
command: شامل مجموعهای از دستورالعملها برای اجرای ماشین جانبی Dapr است.
ResponseByDaprClient : app-id از این نام استفاده می کند. مولفه تفکیک نام از این نام برای یافتن چرخ کناری Dapr مورد نیاز استفاده می کند.
app-port: این شماره پورت با Dockerfile یک پورت سرویس در معرض یکسان خواهد بود.
ServiceOne Dockerfile Expose Port
components: این پیکربندی است که تعیین می کند از کدام حالت ذخیره استفاده شود یا از کدام pub-sub استفاده شود یا از کدام binding استفاده شود و غیره.
network_mode: با استفاده از network_mode، دو کانتینر serviceoneapp-dapr و serviceoneapp را در یک فضای نام قرار دادم. یعنی هر دو دارای IP یکسان و پورت های باز TCP یکسان هستند.
خیلی توضیح دادیم ، حالا ببینیم اصن کار می کنه.
حالا بیایید آزمایش کنیم.
>>>docker compose up deploying....( wink) |
هر برنامه ای میتوان دپر Sidecar را با استفاده از API invoke داخلی که در Dapr تعبیه شده است، فراخوانی کند. API را می توان با HTTP یا gRPC فراخوانی کرد. برای فراخوانی HTTP API از URL زیر استفاده کنید:
http://localhost:<dapr-port>/v1.0/invoke/<application-id>/method/<method-name> |
<dapr-port> پورت HTTP که Dapr به آن گوش می دهد.
<application-id> شناسه برنامه سرویس برای تماس.
<method-name> نام روشی که باید در سرویس راه دور فراخوانی شود.
تماس با ServiceOne sidecar که ServiceTwo را فراخوانی می کند
تماس با ServiceTwo sidecar
اینگونه بود که من فراخوانی سرویس به سرویس را با استفاده از docker-compose، Dapr و یک وب API انجام دادم.
پیشنهاد دوستانه: کد هر میکروسرویس را در یک مخزن جداگانه نگه دارید.
هر گونه نظر یا پیشنهاد بسیار قدردانی خواهد شد.