MS_AOP - NetDevInfraWGinOSSConsortium/NetDevInfraWiki GitHub Wiki

AOP

概要

AOP

  • AOP: Aspect Oriented Programming
  • アスペクト指向プログラミング

AOP とは?

設計目的

  • オブジェクト指向ではうまく分離できない問題

各メソッド内に別のメソッドを呼び出す「処理の共通的パターン」がある場合、
オブジェクト指向ではこれを共通化できず、処理が散在してしまう。

を「アスペクト」と呼び、

  • アスペクト記述言語(若しくはこれを実現するための各種技術)を用いて、
    「アスペクト」を別のメソッド(モジュール)に分離して記述することで、
    プログラムに柔軟性を持たせようとする試み。

※ この問題はIoCでも解決できるが、AOP ではコードの外での解決を実現する。
アスペクト記述言語によるウィービングでコードと分離して記述する。

補足(用語): 「オブジェクト指向ではうまく分離できない問題」は、
一般に**横断的関心事(cross-cutting concern)**と呼ばれる。
AOP の用語では次のように整理される。

用語 意味
アスペクト(aspect) 横断的関心事を切り出したモジュール(ロギング等)
ジョインポイント(join point) 織り込める場所(メソッド呼び出し、例外送出…)
ポイントカット(pointcut) ジョインポイントを選ぶ条件(「*Service の public メソッド」等)
アドバイス(advice) 織り込む処理(前 / 後 / 例外時 / 前後を包む)
ウィービング(weaving) 実際に織り込む作業

ユースケース

アスペクトの代表的な利用例としては「ロギング処理」がある。

  • ロギング処理

ロギング処理(アスペクトの代表的な利用例)

  • ロギング処理のアスペクト
    以下は、AOP を使用してロギング処理の「アスペクト」を
    別のモジュール(or 定義)に分離して実装した例である。

AOPでのロギング処理の実装例

実際にコードが実行される際には、ルールに従って指定されたパターンの
「アスペクト」が織り込まれる(これをアスペクトのウィービングと呼ぶ)。

補足(ロギング以外の代表例): 実務で AOP 的な処理が使われるのは、
概ね次の 5 つに集約される。

関心事 .NET での現在の実現手段
ロギング・計測 ミドルウェアILogger のスコープ、OpenTelemetry
トランザクション TransactionScopeMS-DTC、リポジトリ層
認可 [Authorize] 属性 + フィルター(ASP.NET Core)
例外処理・再試行 IExceptionFilterPollyHttpClient のハンドラー
検証 [Required] 等のデータ注釈、FluentValidation

つまり 「AOP フレームワークを導入する」形ではなく、
フレームワークが用意した拡張点に載せる
形で解決されるのが現在の主流である。

詳細

技術

AOP を実現する技術(方式)には下記のものがある。

補足(.NET での実現方式の整理): .NET では次の 4 通りがある。

方式 実現するもの 代表
IL 書き換え(コンパイル後) 静的ウィービング PostSharp / Metalama、Fody
動的プロキシ(実行時) 動的ウィービング Castle DynamicProxy
透過プロキシRealProxy 動的ウィービング .NET Framework のみ(後述)
ソース ジェネレーター コンパイル時にコード生成 Roslyn(.NETコンパイラ

重要な変化として、原文が扱う RealProxy(透過プロキシ)は
.NET Core 以降で利用できない

System.Runtime.Remoting ごと廃止された)。

現在の代替は、

  • DispatchProxySystem.Reflection.DispatchProxy
    … インターフェイスに対する軽量なプロキシ。RealProxy の後継に近い
  • Castle DynamicProxy
    … クラスの virtual メソッドにも織り込める。DI コンテナーとの統合が容易
  • ソース ジェネレーター
    AOT / トリミングと両立する現在の最有力
// DispatchProxy によるインターセプト(.NET Core 以降)
public class LoggingProxy<T> : DispatchProxy
{
    private T _inner;
    protected override object Invoke(MethodInfo m, object[] args)
    {
        Console.WriteLine($"→ {m.Name}");
        try { return m.Invoke(_inner, args); }
        finally { Console.WriteLine($"← {m.Name}"); }
    }
    public static T Create(T inner) { ... }
}

ただし、実行時プロキシは Native AOT
相性が悪い
System.Reflection.Emit に依存するため)。
AOT を視野に入れるならソース ジェネレーターを選ぶ。

開発基盤

代表的な AOP 開発基盤(古いかも)

言語個別

フレームワーク個別

その他

補足(.NET 側の選択肢/最新化): 原文が挙げるのは
Java 系が中心で、かつ多くが開発終了している
(AspectWerkz は AspectJ に統合、Seasar2 はサポート終了)。

.NET で現在も現役なものを補っておく。

名称 方式 備考
Metalama コンパイル時(Roslyn) PostSharp の後継。商用(無償枠あり)
PostSharp IL 書き換え 商用。長い実績
Castle DynamicProxy 動的プロキシ OSS。Moq / DI コンテナーの土台
Fody IL 書き換え OSS。プラグイン形式(PropertyChanged.Fody 等)
DispatchProxy 動的プロキシ 標準ライブラリ。依存を増やさずに済む

なお、Java の Spring Framework に相当する Spring .NET
事実上メンテナンスされておらず、
.NET では標準の DI(.NET Core における DI)を使うのが定石である。

ウィービング

ウィービングの指定方法には、

  • 定義ファイル
  • アノテーション(メソッド / クラス属性)
  • クラスのメソッド構成

などがある。

前述の説明に合わせると、透過プロキシを使用した AOP では、

  • アスペクトを透過プロキシ(後述)上に実装する。
  • ウィービングされるアスペクトは、使用する透過プロキシにより決定される。

となる。

補足(ウィービングのタイミングによる差): どこで織り込むかによって、
性能・制約・デバッグのしやすさが大きく変わる。

① コンパイル時(IL 書き換え / ソース生成)
     実行時コスト  ほぼゼロ
     制約          ビルド手順に組み込む必要
     AOT           ○(ソース生成なら特に良い)

② 起動時(DI コンテナーがプロキシを生成)
     実行時コスト  生成時のみ
     制約          インターフェイス or virtual メソッドが必要
     AOT           △〜×

③ 呼び出し時(都度リフレクション)
     実行時コスト  高い
     制約          少ない
     AOT           ×

② の「インターフェイス or virtual が必要」という制約は実務で効く。
動的プロキシは継承で差し込む
ため、
sealed クラスや非 virtual メソッドには織り込めない。

関連

参考

AOP、不評ですね(私も全く使ってないです)。

補足(なぜ不評なのか): 原文の率直な感想は、
実務での評価とおおむね一致している。理由を整理しておく。

問題 内容
コードを読んでも分からない 属性 1 つの裏で何が起きるか、定義を追わないと不明
デバッグしにくい スタック トレースにプロキシが挟まる/IL が書き換わっている
ポイントカットが壊れやすい メソッド名の変更で、静かに織り込まれなくなる
性能が読めない 動的プロキシ・リフレクションのコストが見えない
AOT / トリミングと相性が悪い 実行時コード生成に依存する
代替が充実した ミドルウェア、フィルター、ILogger、OpenTelemetry

特に最後の点が大きい。
かつて AOP でしか解けなかった横断的関心事の多くは、
ASP.NET Core のミドルウェア/フィルターや、
HttpClient のデリゲート ハンドラー
といった
**「フレームワークが用意した、見える拡張点」**で解決できるようになった。

// ミドルウェア:パイプラインに「見える形で」並ぶ
app.UseExceptionHandler();
app.UseAuthentication();
app.UseAuthorization();
app.Use(async (ctx, next) => { /* 計測 */ await next(); });

同じ「横断的関心事の分離」を、暗黙のウィービングではなく
明示的な合成で行う
——これが現在の .NET における回答である。
IoC がフレームワーク側の仕組みとして定着した結果、
AOP 専用基盤を持ち込む必要が薄れた、と読むこともできる。


Tags: 移行, プログラミング, .NET開発

⚠️ **GitHub.com Fallback** ⚠️