TSM - AOP și LinFu

Radu Vunvulea - Solution Architect

În ultimele trei numere ale TSM, am descoperit lumea AOP folosind trei stack-uri diferite - .NET, Unity și PostSharp. Dacă ne uităm la aceste framework-uri cu un ochi critic, putem să observăm că PostSharp este un framework extrem de robust care face magia AOP-ului în momentul compilării. Pe când Unity sau .NET stack inserează toată această funcționalitate la runtime.

Run-time vs Build-Time

Este foarte important momentul în care această funcționalitate este injectată deoarece poate să ne afecteze performanța și modul în care aplicația noastră funcționează. În graficul de mai jos putem să vedem o listă cu aceste framework-uri în funcție de momentul în care AOP este injectat în sistem.

După cum putem observa în diagrama de mai sus, cel mai robust framework și flexibil este PostSharp. Acesta injectează în momentul compilării tot ce este nevoie în cod pentru a putea adăuga funcționalitatea de care avem nevoie. Pe când un framework ca Unity nu va modifica codul generat de compilator, iar la runtime va injecta prin reflection și alte mecanisme cu această funcționalitate. Din punct de vedere a performanței, injectarea la runtime a funcționalități are un preț – performanța.

În acest articol vom analiza caracteristicile LinFu-ului. Un framework de AOP este un framework hibrid, o combinație între tipul static și dinamic. Dar înainte să facem acest lucru, haideți să ne aducem puțin aminte ce este AOP.

Recapitulare

AOP este o paradigmă de programare care are drept scop principal creșterea modularității unei aplicații. AOP încearcă să atingă acest scop prin permiterea separării aspectelor secante (cross-cutting concerns) – utilizând interceptarea diferitelor comenzi sau cereri.

În ultimele articole am descoperit cum putem utiliza AOP folosind Unity, PostSharp și proprietăți .NET (RealProxy). Unity ne oferă posibilitatea de a înregistra acțiunile care pot fi executate înainte și după o acțiune specifică. Clasa RealProxy este clasa principală în jurul tuturor acestor proprietăți, care este utilizată de către framework-uri precum Unity pentru a oferi această proprietate.

Cea mai mare diferență dintre RealProxy și un stack care ne oferă AOP este din perspectiva proprietăților. Utilizarea RealProxy în mod direct ne va cere să scriem toată funcționalitatea de care avem nevoie – aceasta se poate traduce în timp, bani și mai mult cod care necesită mentenanță (în cele din urmă, nu dorim să reinventăm roata).

Ce este LinFu

Este un set de librării ce ne oferă suport de AOP plus alte câteva funcționalități precum. Mai jos găsiți lista cu toate aceste funcționalități (am lăsat denumirile în engleză, deoarece traducerea în română suna extrem de ciudat):

Am putea spune că LinFu este o colecție de funcționalități de care am avut nevoie mereu în .NET, dar nu le-am avut niciodată suportate de către .NET stack. Din această cauză am apelat la framework-uri precum LinFu sau Castle (dacă nu ne-am scris propiile noastre framework-uri).

Caracteristica cea mai importantă a LinFu este ușurința cu care poate să fie învățat. Acesta este un framework extrem de simplu, care poate să fie învățat și integrat doar în câteva minute în aplicația noastră.

Deși LinFu are extrem de multe funcționalități, noi ne vom concentra doar asupra celor care sunt legate de AOP. Cât despre celelalte funcționalități, vă las pe voi să le descoperiți.

De unde vine numele

De unde credeți că vine numele de LinFu? Un nume asiatic, al unui programator care a inițiat acest stack? Nu.

Language INdependent Features Underneath [.NET]

La urma urmei LinFu este exact ce spune și numele său. O colecție de assembly-uri care extinde funcționalitatea pe care .NET o are. Această funcționalitate este extinsă folosind librării, fără să modifice sintaxa limbajului, precum alte framework-uri de AOP.

DynamicProxy

DynamicProxy ne ajută să injectăm la runtime cod și funcționalitate în codul nostru fără să modificăm aplicația deja existentă. De exemplu, dacă dorim să injectăm un sistem de trace-ing sau audit fară să fim nevoiți să poluăm codul aplicației noastre.

LinFu ne permite să interceptăm orice metodă la runtime, atâta timp cât este virtuală. Din păcate aceasta este o limitare pe care o are LinFu și alte framework-uri de AOP. Metodele pe care le interceptăm trebuie să fie metode virtuale.

Interceptarea unei metode se poate face implementând una din cele două interfețe pe care DynamicProxy ni le pune la dispoziție IInterceptor sau IInvokeWrapper.

public interface IInterceptor
{
    object Intercept(InvocationInfo info);
}

public interface IInvokeWrapper
{
    void BeforeInvoke(InvocationInfo info);
    object DoInvoke(InvocationInfo info);
    void AfterInvoke(InvocationInfo info, 
    object returnValue);
}

După cum putem observa din definiția celor doua interfețe, putem doar să interceptăm apelul sau să avem un control direct înainte de apel, după apel sau chiar în momentul apelului și să apelăm cu totul altă metodă.

În exemplul de mai jos interceptăm metoda Do din clasa Foo și logăm apelul, fară să apelăm o altă metodă, decât cea originală din clasa Foo.

public class Foo
{
    public virtual int Do(int a, int b)
    {
        return a+b;
    }
}
public class FooInterceptor : IInvokeWrapper
{
    private Foo _target;
    public FooInterceptor(Foo target)
    {
        _target = target;
    }
    public void BeforeInvoke(InvocationInfo info)
    {
        Trace.WriteLine(„Before Do() called”);
    }

    public object DoInvoke(InvocationInfo info)
    {
        object result = null;
        result = info.TargetMethod.Invoke(_target, info.Arguments);
        return result;
    }

    public void AfterInvoke(InvocationInfo info, 
    object returnValue)
    {
        Trace.WriteLine(„After Do() called”);
    }
}

Dacă dorim să apelăm o altă metodă sau să facem ceva specific în momentul apelului, trebuie să adăugăm cod în metoda DoInvoke. Putem chiar să apelăm cu totul o altă metodă, fără să mai apelăm metoda de baza din Foo. Acest lucru se poate face dacă ștergem linia de cod care face apelul propriu zis info.TargetMethod.Invoke(…).

Clasa InvocationInfo conține toată informația de care avem nevoie:

O altă caracteristică a acestui framework este constructorul la interceptor, care primește ca parametru o referință la obiectul propriu zis. Acest lucru se întâmplă deoarece LinFu are nevoie de această referință pentru a putea intercepta apelul și a suprascrie metoda virtuală pe care noi vrem să o interceptăm. Până la urma, LinFu doar redirectează apelurile spre un proxy intern.

Tot ce ne-a mai rămas de făcut este să facem legătura între cele două - clasa Foo și interceptor. Acest lucru realizându-se în felul următor:

ProxyFactory factory = new ProxyFactory();
Foo foo = new Foo();
FooInterceptor fooInterceptor = new FooInterceptor(foo);
Foo customFoo = factory.CreateProxy(interceptor);

Da, codul de mai sus nu arată tocmai foarte frumos, dar poate foarte să fie foarte ușor pus într-o metodă generică și să nu mai fie nevoie să ne batem capul cu acest setup. Odată ce avem o referință la customFoo putem să apelăm fără nici o problemă metoda Do.

Se poate observa foarte clar diferența majoră între PostSharp și LinFu. PostSharp nu cere folosirea unui proxy, deoarece face totul în momentul compilării generând un cod IL care are deja acest hook. Pe când LinFu are nevoie de o configurare din cod.

IProxy

Un lucru foarte interesant la LinFu este interfața IProxy și în special proprietatea "Interceptor". În momentul în care generăm un proxy avem o referință la această interfață. Folosind proprietatea amintită mai sus, putem să schimbăm la runtime interceptorul, fără să fim nevoiți să generăm un nou proxy sau să schimbăm ceva.

IProxy proxy = (IProxy) customFoo;
proxy.Interceptor = fooInterceptor2;

Acest lucru se întâmplă deorece CreateProxy ne returnează o instanță a obiectului nostru ușor modificată. Aceasta implementează interfața IProxy și extinde clasa noastră (în cazul nostru Foo).

Ceea ce se generează de CreateProxy arată asemănator cu:

public class FooProxy : Foo, IProxy
{
    …
    public ovverite int Do(int a, int b)
    {
…
} 
    …
}

Performanță

Din punct de vedere a performanței, LinFu este mult mai rapid ca Castle sau Unity, dar nu se poate compara cu PostSharp. LinFu este extrem de folositor în momentul în care avem foarte multe metode pe care dorim să le controlăm prin intermediul unui AOP framework.

Limitări

LinFu poate să ofere suport de AOP doar pentru metodele virtuale. O metodă care nu este marcată ca virtuală sau face parte dintr-o clasă sealed, nu poate să fie controlată.

Licențiere

Acesta este un framework sub licența GNU. Putem să îl folosim și să îl modificăm fără nici un fel de probleme.

Concluzie

Acesta este ultimul articol despre AOP. În cele patru articole pe care le-am citit până acuma despre AOP, am descoperit diferite mecanisme prin care putem să avem suport AOP în aplicația noastră. Fiecare framework are avantaje și dezavantaje. Putem spune că PostSharp este cel mai complet, având cele mai multe funcționalități și cea mai bună performanță, doar că acest lucru vine cu un cost. PostSharp nu este gratis.

În tabelul de mai jos putem să vedem funcționalitățile suportate de fiecare framework de AOP: