[料理佳餚] 將 Function 序列化為二進位資料之後傳遞給另一個應用程式執行

  • 1003
  • 0
  • C#
  • 2017-12-01

有這個議題是既有系統的 Cache 邏輯在 Cache 沒有命中的時候,會啟動 lock 機制,然後去執行一個由呼叫端傳進來的 delegate function 去後端資料庫重新取得資料,可是我們都知道每家公司多多少少都有遺留一些「初學者程式碼」,這些初學者程式碼不一定是初學者寫的,但它有時候執行的效能並不是很好,在這種情況再搭配 lock 機制之下,後面進來的 Request 就塞住了,進而影響客戶端的響應速度。

較為適當的做法是去 Review 效能不彰的 delegate function,然後改善它,但是這時候「XX 不允許」(XX 可以帶入時間、成本、老闆……等)就會跳出來阻止我們,永遠都有相對更重要的事情要去做,所以別再相信什麼以後再…、晚一點再…、等…的時候再… 的說法,依據 LeBlanc's Law(勒布朗克法則):Later equals never,有些事現在不做以後就不會做了。

我們針對這一類的 delegate function 只好先插管,這邊的做法是將目標 delegate function 與 Web Application 解耦,不要放在 Web Application 上呼叫,在 Cache 快要到期的時候,丟給另一個應用程式去執行並同時更新 Cache。

建立 DelegateWrapper<T> 類別

為了能夠完全掌握 Client 端傳進來的 delegate function 資訊,所以需要調整一下 Client 端的呼叫方式,原本的設計是讓 Clinet 傳遞型別為 Func<TResult> 的 delegate function,現在將它改為傳遞 DelegateWrapper<T> 這個型別的物件,將 Method 及 Parameters 拆開。

[Serializable]
public class DelegateWrapper<TResult>
{
    private readonly Delegate func;
    private readonly string[] jsonParameters;

    public DelegateWrapper(Delegate func, params object[] parameters)
    {
        this.func = func;
        this.jsonParameters = parameters.Select(x => JsonConvert.SerializeObject(x)).ToArray();
    }

    private object[] Arguments
    {
        get
        {
            return this.func.Method.GetParameters()
                .Select((p, i) => JsonConvert.DeserializeObject(this.jsonParameters[i], p.ParameterType))
                .ToArray();
        }
    }

    public TResult Invoke()
    {
        return (TResult)this.func.GetType().GetMethod("Invoke").Invoke(this.func, this.Arguments);
    }
}

有一個 Attribute - [Serializable] 很重要,我們需要把目標 Method 的聲明類別(DeclaringType)也標上 [Serializable] Attribute,而原本 Method 的參數也需要標上 [Serializable] Attribute,但是這邊我借助 Newtonsoft.Json 的力量將參數一個一個序列化為 Json,在 Invoke 的時候根據 Method 本身提供的 ParameterType 再將參數一個一個反序列化回來,原因是參數的型別眾多,我懶得去把參數的型別也一個一個標上 [Serializable] Attribute。

怎麼用?

我假設有一個 Client - FuncSerialization,透過 CacheLibrary 來取得 Cache,當 Cache 快要到期的時候將 delegate function 序列化為二進位資料。

由 FuncSerialization 呼叫 CacheProvider.GetCache() 方法,給入 CacheKeyDelegateWrapper<T> 參數,而 DelegateWrapper 則包裝了一個 CalculatorService.Add 方法及其參數值。

Cache 即將在 1 分鐘之內到期時,將 delegate function 序列化為二進位資料,我這邊暫時以 CacheKey 為檔名,將二進位資料以檔案的方式傳遞。

切換到 FuncDeserialization 將二進位資料檔案讀出來呼叫 Invoke() 方法取得執行結果,記得 FuncDeserialization 需要參考 CacheLibrary 及 ServiceLibrary 組件。

以上都是使用具名型別、方法,我在網路上有看到有人實作了 ISerializable 介面,對匿名的方法做序列化,挺威的,但套用到我場景在 FunDeserialize 反序列化之後卻遺失了參數,對這方面有興趣的朋友可以參考這篇文章 Anonymous Method Serialization,歡迎交流。

 < Source Code >

相關資源

C# 指南
ASP.NET 教學
ASP.NET MVC 指引
Azure SQL Database 教學
SQL Server 教學
Xamarin.Forms 教學