AOP 未來想像 on 2023/4/28

AOP future imagination, Aspect, weave, weaving

AOP - Waving 應用

之後

因為真實世界沒有 AOP weaving 語法,故以 Decorator pattern 取代。

於鷹架碼比主程式碼長的狀況下,AOP 的優勢特別明顯。

Task HandleQuery() => CatchHandling(async () =>
{
  var qryArgs = new TodoQryAgs
    {
      Msg = f_testFail ? "測試邏輯失敗" : "今天天氣真好",
      Amt = 999
    };

  dataList = await bizApi.QryDataListAsync(qryArgs);
});
Task HandleAdd() => CatchHandling(async () =>
{
  var newTodo = await bizApi.AddFormDataAsync(newTodoDesc);

  // Success
  dataList.Add(newTodo);
  newTodoDesc = string.Empty;
});
//# AOP with Decorator
async Task CatchHandling(Func<Task> action)
{
  try
  {
    f_loading = true;
    errMsg = string.Empty;
    await action();
  }
  catch (ApiException ex)
  {
    if (ex.StatusCode == HttpStatusCode.BadRequest)
    {
      var msg = await ex.GetContentAsAsync<ErrMsg>();
      errMsg = $"ApiException: {msg.Severity}-{msg.Message}";
    }
    else
    {
      errMsg = $"ApiException: {ex.Message}";
    }
  }
  catch (Exception ex)
  {
    errMsg = "EXCEPTION: " + ex.Message;
  }
  finally
  {
    f_loading = false;
  }
}

之前

async Task HandleQuery()
{
  try
  {
    f_loading = true;
    errMsg = string.Empty;

    await Task.Delay(500);

    var qryArgs = new TodoQryAgs
      {
        Msg = f_testFail ? "測試邏輯失敗" : "今天天氣真好",
        Amt = 999
      };

    dataList = await bizApi.QryDataListAsync(qryArgs);
  }
  catch (ApiException ex)
  {
    if (ex.StatusCode == HttpStatusCode.BadRequest)
    {
      var msg = await ex.GetContentAsAsync<ErrMsg>();
      errMsg = $"ApiException: {msg.Severity}-{msg.Message}";
    }
    else
    {
      errMsg = $"ApiException: {ex.Message}";
    }
  }
  catch (Exception ex)
  {
    errMsg = "EXCEPTION: " + ex.Message;
  }
  finally
  {
    f_loading = false;
  }
}
async Task HandleAdd()
{
  try
  {
    f_loading = true;
    errMsg = string.Empty;

    var newTodo = await bizApi.AddFormDataAsync(newTodoDesc);

    // Success
    dataList.Add(newTodo);
    newTodoDesc = string.Empty;
  }
  catch (ApiException ex)
  {
    if (ex.StatusCode == HttpStatusCode.BadRequest)
    {
      var msg = await ex.GetContentAsAsync<ErrMsg>();
      errMsg = $"ApiException: {msg.Severity}-{msg.Message}";
    }
    else
    {
      errMsg = $"ApiException: {ex.Message}";
    }
  }
  catch (Exception ex)
  {
    errMsg = "EXCEPTION: " + ex.Message;
  }
  finally
  {
    f_loading = false;
  }
}

以 Blazor App 為例

未導入AOP

FooFormPage.razor
@inject FooFormBiz bizSvc
@injcet IDialogService dlgSvc

@* render dataList *@
@* render formData *@

@code {
  List<FooFormData> dataList = new();
  FooFormData formData = new();
  QryArgs qryArgs = new();
  bool f_block = false;
  
  void HandleQryDataList()
  {
    try {
      blockUI();
      dataList = bizSvc.QryDataList(args);		  
      // refersh UI
    }
    catch(Exception ex) {
      dlgSvc.ShowAlert(ex.Message);
    }
    finally {
      unblockUI();
    }
  }
  
  void HandleGetFormData(FormProfle aim)
  {
    try {
      blockUI();
      formData = bizSvc.GetFormData(aim);  
      // refersh UI
    }
    catch(Exception ex) {
      dlgSvc.ShowAlert(ex.Message);
    }
    finally {
      unblockUI();
    }
  }

  void HandleUpdFormData(FormData formData)
  {
    try {
      blockUI();
      DoVerify(formData);
      bizSvc.UpdFormData(formData);
      // refersh UI
    }
    catch(Exception ex) {
      dlgSvc.ShowAlert(ex.Message);
    }
    finally {
      unblockUI();
    }
  }

  void HandleDelFormData(FormProfle aim)
  {
    try {
      blockUI();
      bizSvc.DelFormData(aim);
      // refersh UI
      dataList.RemoveItem(aim);
    }
    catch(Exception ex) {
      dlgSvc.ShowAlert(ex.Message);
    }
    finally {
      unblockUI();
    }
  }

  void HandleAddFormData(FormData formData)
  {
    try {
      blockUI();
      DoVerify(formData);
      bizSvc.AddFormData(formData);
      // refersh UI
      dataList.InsertItem(fromData.AsProfile());
    }
    catch(Exception ex) {
      dlgSvc.ShowAlert(ex.Message);
    }
    finally {
      unblockUI();
    }
  }
  
  void blockUI()
  {
    f_block = true;
  }

  void unblockUI()
  {
    f_block = false;
  }
}

導入AOP:CatchHandling

FooFormPage.razor
@inject FooFormBiz bizSvc
@injcet IDialogService dlgSvc

@* render dataList *@
@* render formData *@

@code {
  List<FooFormData> dataList = new();
  FooFormData formData = new();
  QryArgs qryArgs = new();
  bool f_block = false;
  
  @CatchHandling[]
  void HandleQryDataList()
  {
    dataList = bizSvc.QryDataList(args);		  
    // refersh UI
  }
  
  @CatchHandling[]
  void HandleGetFormData(FormProfle aim)
  {
    formData = bizSvc.GetFormData(aim);  
    // refersh UI
  }

  @CatchHandling[]
  void HandleUpdFormData(FormData formData)
  {
    DoVerify(formData);
    bizSvc.UpdFormData(formData);
    // refersh UI
  }

  @CatchHandling[]
  void HandleDelFormData(FormProfle aim)
  {
    bizSvc.DelFormData(aim);
    // refersh UI
    dataList.RemoveItem(aim);
  }

  @CatchHandling[]
  void HandleAddFormData(FormData formData)
  {
    DoVerify(formData);
    bizSvc.AddFormData(formData);
    // refersh UI
    dataList.InsertItem(fromData.AsProfile());
  }
     
  void blockUI()
  {
    f_block = true;
  }

  void unblockUI()
  {
    f_block = false;	
  }
  
  Aspect CatchHandling[]
  {
    try {
      blockUI()
      @Target();
    } 
    catch(Exception ex) {
      dlgSvc.ShowAlert(ex.Message);
    } 
    finally {
      unblockUI() 
    }
  } 
}

AOP 語法說明

語法上用符號"@"表示 waveAspect 宣示這是 AOP 的 aspect,之後接它的識別名稱,再後面的"[]"將用來接入參數。 @Target 表示 aspect 欲交織的函式或程式碼區塊,這一個 Aspect 是屬於這個物件專用的。

以 Blazor App 為例二:狀態交換

這個例子我們讓 Aspect 可以共用。我們將把 blockUI、unblockUI 解開。

未導入 AOP

BarFormPage.razor
@inject FooFormBiz bizSvc
@injcet IDialogService dlgSvc

@* render formData *@

@code {
  FooFormData formData = new();
  bool f_block = false;
  
  void HandleSaveFormData(FormData formData)
  {
    try {
      f_block = true;	
      DoVerify(formData);
      bizSvc.SaveFormData(formData);
      // refersh UI
    }
    catch(Exception ex) {
      dlgSvc.ShowAlert(ex.Message);
    }
    finally {
      f_block = false;	
    }
  }
}

導入AOP:CatchHandling

BarFormPage.razor
@inject FooFormBiz bizSvc

@* render formData *@

@code {
  FooFormData formData = new();
  bool f_block = false;
  
  @CatchHandling[f_block]
  void HandleSaveFormData(FormData formData)
  {
    DoVerify(formData);
    bizSvc.SaveFormData(formData);
    // refersh UI
  }  
}
GlobalAspect.cs
Aspect CatchHandling[alias bool blocking]
{
  @injcet IDialogService dlgSvc
  
  try {
    blocking = true;
    @Target();
  } 
  catch(Exception ex) {
    dlgSvc.ShowAlert(ex.Message);
  } 
  finally {
    f_block = false;
  }
}

AOP 語法說明

語法上用符號"@"表示 waveAspect 宣示這是 AOP 的 aspect,之後接它的識別名稱,再後面的"[]"將用來接入參數。 @Target 表示 aspect 欲交織的函式或程式碼區塊,這一個 Aspect 是屬於這個物件專用的。 @inject 為 DI, Dependency Injection 依賴注入。 在 Aspect 起始可以注入需要的資源。 alias 是資訊交換的方式用 Call by alias 這古老的方法,在C#用 Call by reference 似乎是等效的?

更多的 AOP 想像

// 可以掛上多個 AOP
@WhenTrue[this.f_isPowerUser]
@IgnoreException[]
@Retry[500,2]
@CatchHandling[f_block]
void HandleSaveFormData(FormData formData)
{
  DoVerify(formData);
  bizSvc.SaveFormData(formData);
  // refersh UI
}
// 可以同時交織 method 與 code block
@WhenTrue[this.f_isPowerUser]
@IgnoreException[] 
void HandleSaveFormData(FormData formData)
{
  DoVerify(formData);
  
  @Retry[500,2]
  @CatchHandling[f_block]
  {
    bizSvc.SaveFormData(formData);
  }

  // refersh UI
}
// 資訊交換的方式也可以支援物件的 mutable 特性
Aspect CatchHandling(mutable props := {bool f_loaidng, string errMsg })
{
   @inject ActionLogger _logger
   try {
      props.f_loading = true;
      props.errMsg = empty;
      @Target();	 
      _logger.Success("Yes");	  
   }
   catch(Exception ex) {
     props.errMsg = ex.Message;
     _logger.Fail(ex.Message);
   }
   finally {
     props.f_loading = false;
   }
}

Last updated