"> توسعه میکروسرویس ها با NET Core 2.1. و SignalR - بخش سوم | ام اس پی سافت |

توسعه میکروسرویس ها با NET Core 2.1. و SignalR – بخش سوم

میکروسرویس ها با NET Core 2.1.

در این مقاله میخواهیم راجب توسعه میکروسرویس ها با NET Core 2.1. و RabbitMQ, SignalR, EF Core 2.1 , Angular 6  صحبت کنیم. تا پایان این مقاله همراه ما باشید

میتوانید بخش های قبلی مقاله را در لینک‏ زیر مشاهده کنید.

Async Await – Asynchronous Processing

روش The UpdateSalesOrderDetail controller action در API Management Inventory به طور همزمان اجرا نخواهد شد.

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

این امر به این دلیل حاصل می شود که روش های کنترل کننده asynchronous با برگرداندن سریعتر سرور threads به قسمت threads pool، موضوعات را پردازش می کند.

ASP.NET Core 2.1 به کنترل کننده ها API Web اجازه می دهد تا با استفاده از کلمات کلیدی sync await keywords به صورت همزمان و غیر همزمان اجرا شوند.

کلیه روش های عملکرد کنترل کننده در برنامه نمونه async keyword روش signature استفاده می کند.

همه روش های عملکرد کنترل کننده نیز همچنین یک Task containing IactionResult را برمی گردانند.

روش The UpdateSalesOrderDetail controller action همچنین خدمات تجاری Inventory Management را با استفاده از await keyword فراخوانی می کند.

تمام روشهای سرویس مدیریتی موجودی ، الگوی async / await را تا تمام لایه خدمات دسترسی به داده که در آن Entity Framework Core بیانیه های LINQ را بصورت asynchronous اجرا می کند ، پیاده سازی می کند.

برای اجرای صحیح پردازش asynchronous ، هر لایه از برنامه باید عملکردهای asynchronous را در تمام طول فرآیند پیاده سازی کند.

Security, Data Transformation Object and Response Models

قبل از اجرای روش UpdateSalesOrderDetail controller action ، فیلتر کنترل امنیتی اجرا میشود و اطلاعات مربوط را از JSON web token برای جمع کردن Object SecurityModel که به HttpContext اضافه شده است را استخراج میکند.

روش controller action این Object را از طریق HttpContext ارجاع می دهد و شناسه حساب کاربر را به سرویس تجاری Inventory Management منتقل می کند.

استفاده از اطلاعات JSON Web Token روش خوبی برای امنیت داده های برنامه شما است.

روش UpdateSalesOrderDetail controller action از یک دستورالعمل فروش به نام (Data Transformation Object (DTO استفاده می کند.

DTO یک الگوی طراحی است که داده ها را محصور می کند و برای انتقال داده ها بین software application subsystems کاربرد دارد. در نمونه برنامه ، یک DTO واسطه ای بین database مدل های front-end و مدل های back-end است.

سرانجام ،روش UpdateSalesOrderDetail controller action با انجام یک پاسخ HTTP با کد وضعیت (HTTP 200 (OK ، در صورت موفقیت معامله ، یک موضوع ResponseModel را به مشتری بازمیگرداند.

در صورت عدم موفقیت در معامله ،Object ResponseModel با کد وضعیت HTTP 401 (درخواست بد) بازگردانده می شود.

Inventory Management Business Service

هنگامی که روش controller action در خواست های روش UpdateSalesOrderDetail که در سرویس مدیریت تجاری Inventory موجود می باشد را پردازش می کند، موارد زیر با پشتیبانی و اجرای یک کار غیر asynchronous در سرویس های مدیریت تجاری انجام می شود:

  • تأیید کنید که quantity shipped (مقدار حمل شده) برابر صفر نیست
  •  یک database transaction را سریالی سازی کنید
  • کالای the sales order line را با مقدار حمل شده updateکنید
  • یک قفل ردیف منحصر را برای خط محصول update کنید
  • ردیف محصول را updateکنید برای کاهش quantity on hand به وسیله quantity shipped.
  • یک inventory transaction record را برای مقدار ارسال شده ایجاد کنید
  • یک ردیف queue record ظبط شده خروجی همراه با یک سریال JSON string ایجاد کنید تا از آن به عنوان message queue استفاده شود.
  • اجرا و انتقال database را با موفقیت تکمیل کنید.

 

/// 
<summary>
/// Update Sales Order Detail
/// </summary>

/// <param name="salesOrderDetailDataTransformation"></param>
/// <returns></returns>
public async Task<ResponseModel<SalesOrderDetailDataTransformation>> UpdateSalesOrderDetail(
                      SalesOrderDetailDataTransformation salesOrderDetailDataTransformation)
{

    ResponseModel<SalesOrderDetailDataTransformation> returnResponse = 
             new ResponseModel<SalesOrderDetailDataTransformation>();

    SalesOrderDetail salesOrderDetail = new SalesOrderDetail();

    try
    {
        int accountId = salesOrderDetailDataTransformation.AccountId;
        int salesOrderId = salesOrderDetailDataTransformation.SalesOrderId;
        int salesOrderDetailId = salesOrderDetailDataTransformation.SalesOrderDetailId;

        //
        //    Validate Shipped Quantity
        //

        if (salesOrderDetailDataTransformation.CurrentShippedQuantity == 0)
        {
            returnResponse.ReturnMessage.Add("Invalid Shipped Quantity");
            returnResponse.ReturnStatus = false;

            return returnResponse;
        }

        //
        //    Begin a Serializable Transaction
        //

        _inventoryManagementDataService.OpenConnection(  
                                        _connectionStrings.PrimaryDatabaseConnectionString);

        _inventoryManagementDataService.BeginTransaction((int)IsolationLevel.Serializable);

        //
        //    Get Sales Order Header
        //

        SalesOrder salesOrder = await _inventoryManagementDataService
                                      .GetSalesOrderHeader(accountId, salesOrderId);

        if (salesOrder == null)
        {
            _inventoryManagementDataService.RollbackTransaction();

            returnResponse.ReturnMessage.Add("Sales Order not found");
            returnResponse.ReturnStatus = false;

            return returnResponse;
        }

        //
        //    Get Sales Order Detail
        //

        salesOrderDetail = await _inventoryManagementDataService
                                 .GetSalesOrderDetailForUpdate(salesOrderDetailId);

        if (salesOrderDetail == null)
        {
            _inventoryManagementDataService.RollbackTransaction();

            returnResponse.ReturnMessage.Add("Sales Order Detail not found");
            returnResponse.ReturnStatus = false;

            return returnResponse;
        }

        //
        //    Update Sales Order Shipped Quantity
        //

        salesOrderDetail.ShippedQuantity = salesOrderDetail.ShippedQuantity + 
                                           salesOrderDetailDataTransformation.CurrentShippedQuantity;

        await _inventoryManagementDataService.UpdateSalesOrderDetail(salesOrderDetail);

        //
        //    Get Product Record with an exclusive update lock
        //

        Product product = await _inventoryManagementDataService
                                .GetProductInformationForUpdate(salesOrderDetail.ProductId);

        if (product == null)
        {
            _inventoryManagementDataService.RollbackTransaction();

            returnResponse.ReturnMessage.Add("Product not found");
            returnResponse.ReturnStatus = false;

            return returnResponse;
        }

        //
        //    Reduce Product OnHand Quantity by the quantity shipped
        //

        product.OnHandQuantity = product.OnHandQuantity - 
                                 salesOrderDetailDataTransformation.CurrentShippedQuantity;

        await _inventoryManagementDataService.UpdateProduct(product);

        //
        //    Create Inventory Transaction Record
        //

        InventoryTransaction inventoryTransaction = new InventoryTransaction();
        inventoryTransaction.EntityId = salesOrderDetail.SalesOrderDetailId;
        inventoryTransaction.MasterEntityId = salesOrderDetail.MasterSalesOrderDetailId;
        inventoryTransaction.ProductId = salesOrderDetail.ProductId;
        inventoryTransaction.UnitCost = product.AverageCost;
        inventoryTransaction.Quantity = salesOrderDetailDataTransformation.CurrentShippedQuantity;
        inventoryTransaction.TransactionDate = DateTime.UtcNow;

        await _inventoryManagementDataService.CreateInventoryTransaction(inventoryTransaction);

        //
        //    Create Transaction Queue record and create inventory transaction payload
        //

        TransactionQueueOutbound transactionQueue = new TransactionQueueOutbound();
        transactionQueue.Payload = GenerateInventoryTransactionPayload(inventoryTransaction);
        transactionQueue.TransactionCode = TransactionQueueTypes.InventoryShipped;
        transactionQueue.ExchangeName = MessageQueueExchanges.InventoryManagement;

        await _inventoryManagementDataService.CreateOutboundTransactionQueue(transactionQueue);

        await _inventoryManagementDataService.UpdateDatabase();

        //
        //    Commit Transaction
        //

        _inventoryManagementDataService.CommitTransaction();

        returnResponse.ReturnStatus = true;

    }
    catch (Exception ex)
    {
       _inventoryManagementDataService.RollbackTransaction();
       returnResponse.ReturnStatus = false;
       returnResponse.ReturnMessage.Add(ex.Message);
    }
    finally
    {
       _inventoryManagementDataService.CloseConnection();
    }

    returnResponse.Entity = salesOrderDetailDataTransformation;

    return returnResponse;

}

Isolation Levels – Serializable Transactions

Database transactions specify یک سطح isolation را مشخص می کند که تعیین می کند که چگونه یک معامله باید از تغییرات داده توسط سایر معاملات جدا شود.

Isolation levels زمانی به کار گرفته میشود که اثرات جانبی همزان مانند dirty reads یا phantom reads برای انتقال داده ها تعریف شده باشد.

استاندارد SQL چهار سطح جداسازی را تعریف می کند:

  •  Read Uncommitted – Read Uncommitted پایین ترین سطح ایزولاسیون است. در این سطح ، ممکن است یک معامله هنوز تغییراتی که توسط سایر معاملات اعمال شده است را بخواند ، در نتیجه اجازه dirty reads را می دهد. در این سطح ، معاملات از یکدیگر جدا نمی شوند.
  •  Read Committed – این سطح جداسازی تضمین می کند که هر گونه اطلاعات در لحظه خوانده و پردازش می شود. بنابراین اجازه نمی دهد dirty reads انجام گیرد معامله lockخواندن یا نوشتن را روی ردیف فعلی نگه می دارد، بنابراین مانع از خواندن ، به روزرسانی یا حذف سایر معاملات می شود.
  •  Repeatable Read (تکرار خواندن) – این محدود کننده ترین سطح ایزوله است. این، معامله های lock شده و خوانده شده را در تمام ردیف هایی که به آن اشاره می شود ، نگه می دارد و lockهایی را در تمام ردیف هایی که درج می کند ، به روز می کند یا حذف می کند. از آنجا که سایر معاملات قادر به خواندن ، به روزرسانی یا حذف این سطرها نیستند ، بنابراین از non repeatable reads جلوگیری می کند.
  •  Serializable – این بالاترین سطح جداسازی است. در این سطح اطمینان حاصل می شود که serializable execution به صورت serializable اجرا شود. Serializable execution به معنای اجرای عملیاتی است که به نظر می رسد همزمان در حال انجام سریال بندی معاملات است.

به طور پیش فرض ، Entity Framework Core از سطح ایزوله شده Read Committed استفاده می کند.

از آنجا که برنامه ERP نمونه می تواند توسط صدها کاربر به روزرسانی همزمان محصولات و سهام مورد استفاده قرار گیرد ، بیش از یک کاربر می تواند همزمان با یک ردیف جدول پایگاه داده درخواست بروزرسانی کند.

برای اطمینان از یکپارچگی داده ها و جلوگیری از بروزرسانی های فانتوم و از بین رفتن داده ها ، روش UpdateSalesOrderDetail پیشنهاد میشود.

با استفاده از یک معامله serializable ، تضمین می شود که به روزرسانی در ردیف های همان محصول به ترتیبی انجام شود که در آن هر معامله SQL قبل از شروع معاملات SQL بعدی به اتمام برسد.

//
//    Begin a Serializable Transaction
//

_inventoryManagementDataService.OpenConnection(_connectionStrings.PrimaryDatabaseConnectionString);
_inventoryManagementDataService.BeginTransaction((int)IsolationLevel.Serializable);

همانطور که معلوم است ، ایجاد یک معامله serializable برای اطمینان از یکپارچگی داده ها کافی نیست در حالی که چندین به روزرسانی همزمان در همان ردیف های جدول پایگاه داده انجام می شود.

علاوه بر این شما باید هنگام انتخاب یک ردیف ، یک lock به روزرسانی شده در سطح ردیف را بدست آورید.

با استفاده از UPDLOCK SQL Server و انتخاب عبارت SELECT این کار، برای شما انجام می پذیرد.

UPDLOCK مشخص می کند که lockهای بروزرسانی شده باید تا زمان انجام معامله گرفته شده و ذخیره بشوند.

یکی از نکات جالب با جدیدترین نسخه از Entity Framework Core این است که اکنون می توانید عبارت SELECT را که معمولاً Entity Framework Core ایجاد می کند رد کنید.

Entity Framework Core به شما امکان می دهد هنگام کار با یک پایگاه داده رابطه ای ، داخل سؤال های SQL شوید.

این می تواند مفید باشد اگر پرس و جو شما می خواهید انجام دهید با استفاده از LINQ بیان نشده باشد ، زیرا ما می توانیم یک عبارت SQL را با یک UPDLOCK ایجاد کنیم و از روش Entity Framework Core FromSQL برای اجرای جمله SQL با یک lockبروزرسانی شده استفاده کنیم.

مانند هر API که SQL را بپذیرد ، نکته مهم این است که از هر ورودی کاربر برای محافظت در برابر حمله SQL جلوگیری شود.

Entity Framework Core همچنین از نمایش داده های پارامتری پشتیبانی می کند. می توانید متغیرها را در رشته پرس و جو SQL قرار دهید و سپس مقادیر پارامتر را به عنوان آرگومان تهیه کنید.

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

در روش GetProductInformationUpdate از سرویس داده های مدیریت Inventory ، شناسه محصول به عنوان یک آرگومان پارامتری تهیه می شود و ردیف انتخاب شده به سرویس تجاری Inventory Management باز می گردد در حالی که SQL Server قفل آن سطر را نگه می دارد.

/// 
<summary>
/// Get Product Information For Update with exclusive row lock
/// </summary>

/// <param name="productId"></param>
/// <returns></returns>
public async Task<Product> GetProductInformationForUpdate(int productId)
{
    string sqlStatement = "SELECT * FROM PRODUCTS WITH (UPDLOCK) WHERE PRODUCTID = @ProductId";

    DbParameter productIdParameter = new SqlParameter("ProductId", productId);

    Product product = await dbConnection.Products.FromSql(sqlStatement, 
                                                          productIdParameter).FirstOrDefaultAsync();
    return product;

Message Queue Transaction Tables

به عنوان یک طراح ، می خواستم هرکدام از میکروسرویس ها دارای یک سری حاشیه باشند و از مرزها عبور نکنند یا تماس های از راه دور را به سایر میکروسرویس ها انجام دهند.

مانند اکثر architectural decision (طراحی معماری)،هزینه ای برای ورود وجود دارد.

در این حالت ، داده ها باید در میان میکروسرویس ها به اشتراک گذاشته شوند.

برای پشتیبانی از isolation میکروسرویس ،قیمت ورودی کپی اطلاعات database در بیش از یک میکرو سرویس وجود دارد.

به عنوان مثال ، اطلاعات مربوط به محصول در میکروسرویس مدیریت و نگهداری می شود.

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

جداول و داده های محصول باید در هر یک از این میکروسرویس ها وجود داشته باشد.

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

ساختار جدول Product بین میکروسرویس ها متفاوت است.

به عنوان مثال ، جدول محصول در database مدیریت موجودی شامل تمام اطلاعات مربوط به یک محصول خواهد بود ، اما پایگاه داده مدیریت سفارش یا پایگاه داده مدیریت فروش نیاز به پیگیری مواردی مانند محل جمع آوری محصول و سایر اطلاعات مربوط به warehouse (مخزن) ندارد.

به عنوان طراح بخشی از این طرح ، می خواستم queue message و payloads را ایجاد کنم که بتوانند قبل از ارسال هرگونه پیام به RabbitMQ ، در یک معامله تجاری باdatabase ادغام میشوند.

این تضمین می کند که پیام ها هرگز گم نمی شوند و در صورت لزوم می توانند وارد سیستم شوند و از آن خارج شوند.

برای هر میکروسرویس ، چهار جدول زیر در هر پایگاه داده اختصاصی میکروسرویس برای پردازش queue messages و ورود به فعالیت آنها ایجاد شده است.

  • TransactionQueueInbound : حاوی اطلاعات message queue payload برای پیام های ورودی آماده برای پردازش است.
  • TransactionQueueInboundHistory : حاوی message queue ورودی بایگانی شده می باشد که به طور کامل پردازش می شوند.
  •  TransactionQueueOutbound  : حاوی اطلاعات message queue payload برای پیام های خروجی آماده پردازش و ارسال آن ها است
  •  TransactionQueueOutboundHistory : حاوی queue messages خروجی بایگانی شده است به طور کامل پردازش می شوند

Creating a Message Queue Message Payload

یکی از قطعات یک queue message پیام بارگذاری (payload) آن است.

payload داده ای است که می خواهید انتقال دهید.

برای نمونه payload اطلاعات برای ارسال اطلاعات به Queue Message در جدول TransactionOutboundQue ذخیره می شود.

در روش UpdateSalesOrderDetail از سرویس تجاری مدیریت Inventory ، یک معامله موجود را به بانک اطلاعاتی میفرستد، معاملات موجود به یک ساختار JSON سری می شوند و به عنوان یک رشته در جدول TransactionOutboundQueue ذخیره می شوند که بعداً بازیابی می شوند.

در قسمت table درج شده است.

/// 
<summary>
/// Generate Inventory Transaction Payload
/// </summary>

/// <param name="inventoryTransaction"></param>
/// <returns></returns>
private string GenerateInventoryTransactionPayload(InventoryTransaction inventoryTransaction)
{
    InventoryTransactionPayload inventoryTransactionPayload = new InventoryTransactionPayload();

    inventoryTransactionPayload.ProductId = inventoryTransaction.ProductId;
    inventoryTransactionPayload.Quantity = inventoryTransaction.Quantity;
    inventoryTransactionPayload.UnitCost = inventoryTransaction.UnitCost;
    inventoryTransactionPayload.EntityId = inventoryTransaction.EntityId;
    inventoryTransactionPayload.MasterEntityId = inventoryTransaction.MasterEntityId;
    inventoryTransactionPayload.TransactionDate = inventoryTransaction.TransactionDate;

    string payload = SerializationFunction<InventoryTransactionPayload>
                     .ReturnStringFromObject(inventoryTransactionPayload);

    return payload;

}

RabbitMQ Best Practices

در این مرحله از فرآیند ، ما یک معامله موجودی حمل و نقل داریم که به database مدیریت موجودی متعهد است اما هنوز به میکروسرویس مدیریت فروش (sales order) اطلاع داده میشود که یک سفارش ارسال شده است.

سفارش فروش در database، inventory management به روز می شود اما باید در database ، sales order نیز به روز شود.

قبل از پیشبرد و اجرای RabbitMQ برای ارسال پیام از میکروسرویس مدیریت Inventory به میکروسرویس مدیریت sales order، می خواستم در مورد بهترین شیوه های RabbitMQ اطلاعات بیشتری کسب کنم.

برخی از برنامه ها به کارایی بسیار بالایی احتیاج دارند.

هدف شما هنگام طراحی سیستم باید به حداکثر رساندن ترکیبی از عملکرد و در دسترس بودن باشد که برای کاربرد خاصی معنی دارد. تصمیمات یا اشکالات طراحی معماری می تواند به میزان توانایی شما آسیب برساند یا تأثیر منفی بگذارد.

بهترین روش ها: هر اتصال RabbitMQ حدود ۱۰۰ KB رم (و حتی بیشتر در صورت استفاده از TLS) استفاده می کند.

هزاران اتصال می تواند بار سنگینی بر روی سرور RabbitMQ بگذارد.

در بدترین حالت ، سرور به دلیل خروج از حافظه می تواند خراب شود.

پروتکل AMQP دارای مکانیسمی کانالهایی است که یک اتصال TCP واحد را “چند برابر” می کنند.

توصیه می شود که هر فرآیند فقط یک اتصال TCP ایجاد کند و از چندین کانال در آن اتصال برای موضوعات مختلف استفاده کند.

اتصالات همچنین باید طولانی باشند. فرایند برای اتصال AMQP حداقل به ۷ بسته TCP نیاز دارد (در صورت استفاده از TLS).

  •  کانال ها را بین موضوعات به اشتراک نگذارید – همچنین باید اطمینان حاصل کنید که کانال ها را بین موضوعات به اشتراک نمی گذارید ، زیرا بیشتر مشتریان کانال های ایمنی را ایجاد نمی کنند ، زیرا این امر تأثیر منفی جدی بر عملکرد دارد.
  •  اتصالات یا کانال ها را به طور مکرر باز و بسته نکنید – در صورت اتصالات طولانی از کانال های یکسان برای هر کار استفاده کنید. فرایند اتصال AMQP کاملاً پیچیده است. در صورت لزوم کانال ها می توانند بیشتر و بیشتر باز شوند و بسته شوند ، اما در صورت امکان باز یا بسته بودن کانال ها نیز باید طولانی مدت باشند ، به عنوان مثال استفاده مجدد از همان کانال در هر موضوع برای انتشار. هر بار که انتشار می دهید ، کانال باز نکنید. اگر نمی توانید اتصالات طولانی مدت داشته باشید ، پس از اتصال درست مطمئن شوید.
  •  اتصالات جداگانه برای ناشر و مصرف کننده – برای دریافت توان بالا ، اتصالات جداگانه ای را برای انتشار و مصرف پیام ایجاد کنید. RabbitMQ می تواند فشار بیشتری را به اتصال TCP اعمال کند زمانی که ناشر در حال ارسال پیام های زیادی به سرور برای رسیدگی است. اگر شما در همان اتصال TCP مصرف می کنید ، ممکن است سرور تأییدیه های پیام را از مشتری دریافت نکند. بنابراین ، عملکرد مصرفی نیز تحت تأثیر قرار خواهد گرفت. و با سرعت کمتر سرور دچار مشکل می شود.

ASP.NET Core 2.1 Scalability

خواندن بهترین شیوه های RabbitMQ باعث شد تا باور کنم که پیوست و پیاده سازی RabbitMQ به طور مستقیم در برنامه web API ایده خوبی نیست.

حافظه و منابع سرور وب باید محدود در نظر گرفته شوند.

برنامه های ASP.NET Core Web API به گونه ای طراحی شده اند که دارای برنامه هایی بدون تابعیت با موضوعاتی هستند که به طور مداوم بنا به درخواست وب ایجاد می شوند و از بین می روند و بنابراین حافظه را آزاد می کنند و مقیاس پذیری برنامه را افزایش می دهند. نگه داشتن منابع ، با افزایش پایگاه کاربر ، باعث افزایش مصرف حافظه سرور می شود.

همانطور که در بهترین روش ها و توصیه های آنها بیان شده است ، اتصالات RabbitMQ بدون اتصال و باز کردن مکرر اتصالات باید انجام شود.

برای ارسال پیام باید یک اتصال جداگانه ایجاد شود ، به این معنی که شما حداقل نیاز به ایجاد دو موضوع مجزا جداگانه در برنامه API web دارید.

به نظر می رسد چندین موضوع Singleton برای برنامه ASP.NET Core Web API بدون تابعیت است.

یک object classesبا طول عمر singleباید برای ایمنی نیز مدیریت شود.

اگر به درستی انجام نشود می تواند یک اشکال در باگ web API ایجاد کند. اشکال در باگ هنگامی رخ می دهد که دو یا چند موضوع به طور به یک blockخاص از کد برسند که وضعیت خاص را ایجاد می کند.

اجتناب از این نیاز به قفل کردن کد دارد تا فقط یک موضوع در یک زمان بتواند بلاک کد را به طور همزمان اجرا کند.

به نظر می رسد قفل کردن کد در یک برنامه web API باعث ایجاد تنگنا و کاهش مقیاس پذیری برنامه می شود وقتی صدها کاربر همزمان به برنامه شما دسترسی داشته باشند.

namespace CodeProject.InventoryManagement.WebApi.SignalRHub
{
    public class MessageQueueHub : Hub
    {

    }
}
  • پسورد: www.mspsoft.com
برچسب‌ها:
زهره سلطانیان

نوشته‌های مرتبط

دیدگاه‌ها

*
*

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