العلاقة بين C # و C #: REST و gRPC وكل شيء بينهما

هناك طرق عديدة للتواصل بين عميل C # وخادم C #. بعضها موثوق به والبعض الآخر ليس كذلك. بعضها سريع للغاية ، والبعض الآخر ليس كذلك. من المهم معرفة الخيارات المختلفة حتى تتمكن من تحديد الخيار الأفضل بالنسبة لك. ستناقش هذه المقالة التقنيات الأكثر شيوعًا حتى الآن وسبب استخدامها على نطاق واسع. سنتحدث عن REST و gRPC وكل شيء بينهما.

السيناريو الأمثل


دعونا نلقي نظرة على الكيفية التي نود أن يبحث بها اتصال العميل بالخادم في العالم الحقيقي. أقدم شيء مثل هذا:

// on client side
public void Foo()
{
    var server = new MyServer(new Uri("https://www.myserver.com/");)
    int sum = server.Calculator.SumNumbers(12,13); 
}

// on server side
class CalculatorController : Controller{
    public int SumNumbers(int a, int b)
    {
        return a + b;
    }
}

أود الحصول على دعم Intellisense الكامل. عندما انقر فوق الخادم و . أريد أن يعرض Visual Studio جميع وحدات التحكم. وعندما أنقر فوق CalculatorController و . أريد أن أرى كل طرق العمل. أرغب أيضًا في الحصول على أعلى أداء ، وتحميل منخفض جدًا للشبكة ، ونقل بيانات ثنائي الاتجاه. وأحتاج إلى نظام موثوق به يتواءم مع التحكم في الإصدار حتى أتمكن بسهولة من نشر إصدارات جديدة من العملاء وإصدارات جديدة من الخوادم.

هذا كثير؟

لاحظ أنني أتحدث هنا عن واجهات برمجة التطبيقات عديمة الحالة . هذا يعادل مشروع C # حيث يوجد نوعان فقط من الفئات:

  • فصول ثابتة بطرق ثابتة فقط.
  • فئات POCO التي لا يوجد فيها سوى الحقول والخصائص التي يكون نوعها بدائيًا أو فئة POCO أخرى.

إن وجود الدولة في واجهة برمجة التطبيقات يقدم تعقيدًا ، وهذا هو أصل كل الشرور. لذا ، من أجل هذه المقالة ، دعونا نجعل الأشياء جميلة وعديمة الجنسية.

نهج REST التقليدي


ظهرت REST API في أوائل العقد الأول من القرن الحادي والعشرين وغزت الإنترنت. الآن هذه هي الطريقة الأكثر شيوعًا لإنشاء خدمات الويب. تحدد

REST مجموعة ثابتة من عمليات GET و POST و PUT و DELETE للطلبات من العميل إلى الخادم. لكل طلب ، نحصل على رد يحتوي على حمولة (عادةً JSON). تتضمن الطلبات معلمات في الطلب نفسه أو كحمولة (عادةً JSON) عندما يكون طلب POST أو PUT.

هناك معيار RESTful API الذي يحدد القواعد التالية (التي لا تحتاج إليها حقًا):

  • يتم استخدام GET للحصول على المورد
  • يستخدم PUT لتغيير حالة المورد.
  • يستخدم POST لإنشاء مورد.
  • يستخدم DELETE لحذف مورد.

إذا لم تكن على دراية بـ REST حتى الآن ، فمن المحتمل ألا يساعد الشرح أعلاه ، لذا هنا مثال. .NET يحتوي على دعم REST مضمن. في الواقع ، ASP.NET Web API بشكل افتراضي خدمة ويب REST. إليك ما يبدو عليه عميل وخادم ASP.NET النموذجي:

على الخادم:

[Route("People")]
public class PeopleController : Controller
{
    [HttpGet]
    public Person GetPersonById(int id)
    {
        Person person = _db.GetPerson(id);
        return person;//Automatically serialized to JSON
    }
} 

على العميل:

var client = new HttpClient();
string resultJson = await client.GetStringAsync("https://www.myserver.com/People/GetPersonById?id=123");
Person person = JsonConvert.DeserializeObject<Person>(resultJson);

REST ملائم تمامًا ، ولكنه ليس مناسبًا للسيناريو الأمثل. لذلك دعونا نرى ما إذا كان بإمكاننا القيام بذلك بشكل أفضل.

مجددة


ReFit ليس بديلاً عن REST. بدلاً من ذلك ، فهي مبنية على قمة REST وتسمح لنا باستدعاء نقاط نهاية الخادم كما لو كانت طريقة بسيطة. يتم تحقيق ذلك من خلال مشاركة الواجهة بين العميل والخادم. على جانب الخادم ، ستنفذ وحدة التحكم الواجهة:

public interface IMyEmployeeApi
{
    [Get("/employee/{id}")]
    Task<Employee> GetEmployee(string id);
}

ثم على جانب العميل ، ستحتاج إلى تمكين نفس الواجهة واستخدام الكود التالي:


var api = RestService.For<IMyEmployeeApi>("https://www.myserver.com");
var employee = await api.GetEmployee("abc");

الأمر بسيط للغاية. ليست هناك حاجة لتشغيل الأتمتة المعقدة أو استخدام أدوات الطرف الثالث ، باستثناء بعض حزم NuGet.

هذا يقترب كثيرًا من السيناريو الأمثل. الآن لدينا IntelliSense وعقد موثوق به بين العميل والخادم. ولكن هناك خيار آخر أفضل من بعض النواحي.

اختيال


مثل ReFit ، تم بناء Swagger أيضًا على قمة REST. OpenAPI ، أو التبختر ، هي مواصفات REST API. يصف خدمة ويب REST في ملفات JSON البسيطة. هذه الملفات عبارة عن مخطط API لخدمة الويب. يشملوا:

  • جميع المسارات (عناوين URL) في واجهة برمجة التطبيقات.
  • العمليات المتوقعة (GET ، POST ، ...) لكل مسار. يمكن لكل مسار معالجة عمليات مختلفة. على سبيل المثال ، يمكن أن يقبل المسار نفسه mystore.com/Product عملية POST التي تضيف منتجًا وعملية GET التي تُرجع منتجًا.
  • المعلمات المتوقعة لكل مسار وعملية.
  • الإجابات المتوقعة لكل مسار.
  • أنواع كل معلمة وكائن استجابة.

ملف JSON هذا هو في الأساس عقد بين العملاء والخادم. فيما يلي مثال لملف التبجح الذي يصف خدمة ويب تسمى Swagger Petstore (للتوضيح ، قمت بإزالة بعض الأجزاء):

مخطط Json
{ 
   "swagger":"2.0",
   "info":{ 
      "version":"1.0.0",
      "title":"Swagger Petstore",
      "description":"A sample API that uses a petstore as an example to demonstrate features in the swagger-2.0 specification",
   },
   "host":"petstore.swagger.io",
   "basePath":"/api",
   "schemes":[ 
      "http"
   ],
   "consumes":[ 
      "application/json"
   ],
   "produces":[ 
      "application/json"
   ],
   "paths":{ 
      "/pets":{ 
         "get":{ 
            "description":"Returns all pets from the system that the user has access to",
            "operationId":"findPets",
            "produces":[ 
               "application/json",
               "application/xml",
            ],
            "parameters":[ 
               { 
                  "name":"tags",
                  "in":"query",
                  "description":"tags to filter by",
                  "required":false,
                  "type":"array",
                  "items":{ 
                     "type":"string"
                  },
                  "collectionFormat":"csv"
               },
               { 
                  "name":"limit",
                  "in":"query",
                  "description":"maximum number of results to return",
                  "required":false,
                  "type":"integer",
                  "format":"int32"
               }
            ],
            "responses":{ 
               "200":{ 
                  "description":"pet response",
                  "schema":{ 
                     "type":"array",
                     "items":{ 
                        "$ref":"#/definitions/Pet"
                     }
                  }
               },
...
 


دعونا نلقي نظرة على عواقب ذلك. باستخدام ملف JSON مثل الملف أعلاه ، يمكنك إنشاء عميل C # باستخدام التحسس الذكي الكامل. في النهاية ، تعرف جميع المسارات والعمليات وما هي المعلمات التي يتوقعونها وأنواع المعلمات وما هي الإجابات.

هناك العديد من الأدوات التي تفعل ذلك. على جانب الخادم ، يمكنك استخدام Swashbuckle.AspNetCore لإضافة Swagger إلى ASP.NET الخاص بك وإنشاء ملفات JSON المحددة. بالنسبة إلى جانب العميل ، يمكنك استخدام swagger-codegen و AutoRest لمعالجة هذه الملفات بتنسيق JSON وإنشاء العميل. دعنا نرى مثالاً لكيفية القيام بذلك:

إضافة Swagger إلى خادم ASP.NET الخاص بك


بدء بإضافة NuGet Swashbuckle.AspNetCore حزمة . في ConfigureServices ، قم بتسجيل مولد Swagger:

services.AddSwaggerGen(options => 
	options.SwaggerDoc("v1", new OpenApiInfo {Title = "My Web API", Version = "v1"}));

في ملف Startup.cs في طريقة التكوين ، أضف:

app.UseSwagger();

أخيرًا ، يجب تمييز طرق الإجراءات داخل وحدة التحكم بسمات [HttpXXX] و [FromXXX] :

[HttpPost]
public async Task AddEmployee([FromBody]Employee employee)
{
    //...
}
 
[HttpGet]
public async Task<Employee> Employee([FromQuery]string id)
{
    //...
}

الأمر بسيط للغاية بالنسبة للخادم. عندما يبدأ المشروع ، سيتم إنشاء ملف swagger.json ، والذي يمكنك استخدامه لإنشاء العميل.

إنشاء عميل من Swagger باستخدام AutoRest


لبدء استخدام AutoRest ، قم بتثبيته باستخدام npm : npm install -g autorest . بعد التثبيت ، ستحتاج إلى استخدام واجهة سطر الأوامر AutoRest لإنشاء عميل C # من ملف swagger.json . هنا مثال:

autorest --input-file="./swagger.json" --output-folder="GeneratedClient" --namespace="MyClient" --override-client-name="MyClient" --csharp

سيؤدي هذا إلى إنشاء مجلد GeneratedClient مع ملفات C # التي تم إنشاؤها. لاحظ أنه تم إعادة تعريف مساحة الاسم واسم العميل. أضف هذا المجلد إلى مشروع العميل الخاص بك في Visual Studio كما هو موضح أدناه.



ستحتاج إلى تثبيت حزمة Microsoft.Rest.ClientRuntime NuGet ، لأن الشفرة التي تم إنشاؤها تعتمد عليها. بعد التثبيت ، يمكنك استخدام واجهة برمجة التطبيقات مثل فئة C # العادية:

var client = new MyClient();
Employee employee = client.Employee(id: "abc");

توجد بعض التفاصيل الدقيقة التي يمكنك القراءة عنها في وثائق AutoRest. وستحتاج إلى أتمتة هذه العملية ، لذلك أقترح قراءة دليل باتريك سفينسون للحصول على بعض النصائح المفيدة ، بالإضافة إلى هذه المقالة لبيتر يوسوفيتس.

مشكلتي في Swagger هي أن ملف JSON يتم إنشاؤه في وقت التشغيل ، لذلك هذا يجعل الأمر صعبًا بعض الشيء لأتمتة عملية CI / CD.

REST التقليدية مقابل Swagger مقابل ReFit


إليك بعض النقاط التي يجب مراعاتها عند الاختيار.

  • إذا كان لديك REST API خاص بسيط للغاية ، فقد لا تقلق بشأن إنشاء العميل والواجهات الشائعة. مهمة صغيرة لا تبرر بذل جهد إضافي.
  • يدعم Swagger العديد من اللغات ، و ReFit يدعم .NET فقط. Swagger هو أيضًا أساس للعديد من الأدوات والاختبارات وأدوات الأتمتة وأدوات واجهة المستخدم. قد يكون هذا هو الخيار الأفضل إذا كنت تقوم بإنشاء واجهة برمجة تطبيقات عامة كبيرة.
  • Swagger أكثر تعقيدًا بكثير من ReFit. مع ReFit ، فإن الأمر يتعلق فقط بإضافة واجهة واحدة إلى كل من خادمك ومشروع العميل. من ناحية أخرى ، مع ReFit ، سيتعين عليك إنشاء واجهات جديدة لكل وحدة تحكم ، بينما تعتني Swagger بذلك تلقائيًا.

ولكن قبل أن تقرر شيئًا ما ، تحقق من الخيار الرابع ، الذي لا علاقة له بـ REST.

gRPC


gRPC (gRPC - استدعاء الإجراء البعيد) هو نظام استدعاء إجراء بعيد مفتوح المصدر تم تطويره بواسطة Google. هذا يشبه إلى حد ما REST ، بمعنى أنه يوفر طريقة لإرسال الطلبات من العميل إلى الخادم. لكن هذا مختلف تمامًا ، فيما يلي أوجه التشابه والاختلاف:

  • مثل REST ، gRPC هي لغة مستقلة. هناك أدوات لجميع اللغات الشائعة ، بما في ذلك C #.
  • gRPC قائم على العقد ويستخدم ملفات .proto لتحديد العقد. هذا يشبه إلى حد ما Swagger swagger.json وواجهة ReFit الشائعة. يمكن إنشاء عميل لأي لغة برمجة من هذه الملفات.
  • gRPC Protocol Buffer (Protobuf). REST, JSON XML. , , .
  • gRPC HTTP/2. . , REST HTTP 1.x ( HTTP 1.1).
  • HTTP 1.1 TCP- () , HTTP/2 .
  • HTTP/2 . , TCP- . , , HTTP 1.1.
  • gRPC .

هناك طريقتان لاستخدام gRPC. بالنسبة إلى .NET Core 3.0 ، هناك gRPC مُدار بالكامل لمكتبة .NET . كما يمكنك استخدام gRPC C # . هذا لا يعني أن gRPC لـ .NET يحل محل gRPC C # . دعنا نرى مثالاً مع gRPC الأحدث لـ .NET .

GRPC لجانب خادم .NET


هذا ليس دليلاً ، بل فكرة عامة عما يمكن توقعه. في ما يلي الشكل الذي ستبدو عليه وحدة التحكم في gRPC:


public class GreeterService : Greeter.GreeterBase
{
    public override Task<HelloReply> SayHello(HelloRequest request,
        ServerCallContext context)
    {
        _logger.LogInformation("Saying hello to {Name}", request.Name);
        return Task.FromResult(new HelloReply 
        {
            Message = "Hello " + request.Name
        });
    }
}

تحتاج إلى إضافة ما يلي إلى تكوين في ملف Startup.cs :


app.UseEndpoints(endpoints =>
{
    endpoints.MapGrpcService<GreeterService>();
});

تم وصف واجهة برمجة التطبيقات في ملف .proto ، وهو جزء من المشروع:

syntax = "proto3";
 
service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}
 
message HelloRequest {
  string name = 1;
}
 
message HelloReply {
  string message = 1;
}

تمت إضافة ملف .proto هذا في ملف .csproj:

<ItemGroup>
  <Protobuf Include="Protos\greet.proto"/>
</ItemGroup>

GRPC لجانب عميل .NET


يتم إنشاء العميل من ملفات .proto . الكود نفسه بسيط للغاية:


var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel);
 
var response = await client.SayHello(
    new HelloRequest { Name = "World" });
 
Console.WriteLine(response.Message);

gRPC مقابل REST


يبدو GRPC صفقة جميلة. إنه أسرع وأسهل تحت غطاء المحرك. هل يجب علينا جميعًا التبديل من REST إلى gRPC؟ الجواب هو أنه يعتمد . إليك بعض الاعتبارات:

في تجربتي ، لا يزال العمل مع gRPC و ASP.NET صغيرًا. ستكون أفضل حالًا مع دعم REST الناضج. فيما يتعلق بالاتصال التعاقدي ، هذا أمر جيد ، باستثناء أن لديك بدائل مماثلة لـ REST التي تحدثنا عنها بالفعل: Swagger و ReFit.

أكبر ميزة هي الأداء. في معظم الحالات ، وفقًا لهذه المعايير ، يكون gRPC أسرع بكثير. خاصة بالنسبة للحمولات الكبيرة التي يكون لها تسلسل Protobuf حقًا. هذا يعني أن هذه ميزة كبيرة لخادم ذو حمولة عالية.

من الصعب الانتقال من REST إلى gRPC في تطبيق ASP.NET كبير. ومع ذلك ، إذا كان لديك بنية قائمة على الخدمات الدقيقة ، فسيصبح إكمال هذا الانتقال أسهل كثيرًا.

طرق أخرى للتواصل


هناك العديد من طرق الاتصال الأخرى التي لم أذكرها على الإطلاق ، ولكن من الجدير معرفة أنها موجودة:

  • GraphQL هي لغة استعلام API تم تطويرها بواسطة Facebook. يسمح هذا للعميل بطلب البيانات التي يحتاجها بالضبط من الخادم. وبالتالي ، يمكنك إنشاء نقطة نهاية واحدة فقط على الخادم ، والتي ستكون مرنة للغاية وستعيد فقط البيانات التي يحتاجها العميل. أصبح GraphQL شائعًا جدًا في السنوات الأخيرة.
  • TcpClient TcpListener ( System.Net.Sockets) TCP. , . , ASP.NET API.
  • UdpClient UDP. TCP , UDP . TCP , , UDP — . UDP , , . : , IP (VoIP).
  • WCF هي تقنية قديمة تستخدم بشكل أساسي الاتصالات بين العمليات القائمة على SOAP. هذا إطار عمل ضخم لن أخوض فيه ، لا يسعني إلا أن أقول أنه فقد شعبيته في حمولات REST و JSON.

هذا كل شئ. آمل أن تكون المقالة مثيرة للاهتمام! كود جيد للجميع!

All Articles