Programlamada kodlar uzadıkça kodların anlaşılabilirliği düşmektedir ve çoğu zamanda içinden çıkılmaz bir hal almaktadır. Tek bir "basit" kayıt işlemini yapan kod bloğu bile tüm uygulamada geçerli olan hata yakalama, loglama, yetki kontrolü gibi çapraz modüllere ait kodlardan dolayı "karmaşıktır". Örneğim aşağıda bu "basit" kayıt işlemine bir örnek verilmiştir.
[cs]
public void SaveOrderToDB (Order order, User user)
{
LogManager .BeginPerformanceCounter ("SaveOrderToDB" );
if (order == null || user == null )
throw new NullReferenceException ();
if (!user.IsInRole ("Sales" ))
throw new SecurityException ();
var totalAmount = order.Unit *order.UnitAmount ;
var balance = GetBalanceFormAccount (order.AccountId );
if (totalAmount > balance)
throw new ApplicationException ();
try
{
var dbContext = new DbContext ();
dbContext.Orders .Add (order);
dbContext.SaveChanges ();
}
catch (Exception exception)
{
LogManager .EndPerformanceCounter ("SaveOrderToDB" );
LogManager .SaveException (exception);
throw ;
}
LogManager .EndPerformanceCounter ("SaveOrderToDB" );
}
Burada görüldüğü üzere müşterinin hesabından yapılan basit bir ödeme işleminde bile kodu karmaşık hale getiren bir çok çapraz modül söz konusudur. Öncelikle fonksiyona girerken loglama modlü çalıştırılıyor sonra parametrelerin doğrulanması daha sonra işlemi yapan kullanıcının yetki doğrulaması yapılıyor. Daha sonra hata kontrolleri ve en son fonksiyondan cıkmadan önce tekrar loglama yapılıyor. Aslında yapılmak istenen sadece veri tabanına doğru bir kayıt eklemektir. Buda 26 satırlık fonksyonumuzda ki 9 satırda ya yapılmaktadır. Poliçe enjekte veya Cephe Yönelimli (Asspect Oriented) programlama yaklaşımı bu çapraz modülleri mümkün olduğunca bir noktadan yönetilmesini ve fonksiyonun içinde ki kod bloğundan uzaklaştırılmasını amaçlamaktadır. Eğer bu fonksiyonu Policy Injection kullanarak yazmış olsaydık aşağıdaki şekilde görülecekti:
[cs]
[PerformanceCounterHandler ]
[ExceptionHandler ]
[CheckUserIsInRoleHandler ("Sales" )]
[CheckArgumentsHandler ]
public void SaveOrderToDB (Order order, User user)
{
var totalAmount = order.Unit * order.UnitAmount ;
var balance = GetBalanceFormAccount (order.AccountId );
if (totalAmount > balance)
throw new ApplicationException ();
var dbContext = new DbContext ();
dbContext.Orders .Add (order);
dbContext.SaveChanges ();
}
Görüldüğü üzere kodlama daha temiz ve sade oldu. Bu fonksiyonda tüm gerekli çapraz modüller fonksiyona bir poliçe olarak eklenmektedir. Fonksiyona yapılan çağrıda resimde görüldüğü şekilde bir sorumluluk zinciri takip edilmektedir. Önce Performans counter modülü sonra sırası ile diğer modüller çalışacak en son metot çalışacaktır. Daha sonra sonuç aynı yolu izleyerek geri döndürülecektir. Bu yapıya sorumluluk zinciri denmektedir. Yukarıda ki örneğimizde sorumluluk zinciri şu şekilde çalışacaktır: Performans Counter -> Exception Handler -> Sheck User Is In Role -> CheckArguments -> Metot -> Check Arguments -> Check User Is In Role -> Exception Handler -> Performans Counter. Bu sıralama sizi korkutmasın performans olarak kaybınız mili saniyelerin altındadır. Kod sadeliği ve yönetim kolaylığı olarak kazançınız çok daha fazla olacaktır.
Peki projemizde ki çapraz modülleri fonksiyonlarımıza birer poliçe olarak nasıl enjekte edebiliriz ?
Her şeyden önce poliçe enjekte edebilmek için nesnenin her hangi bir metotu çağrıldığında tetiklenecek bir yönetim fonksiyonuna ihtiyaçımız var. Yani sınıfa ait property'ler değiştiği zaman fırlatılan PropertyOnChanged olayına benzer, fonksiyon çağrılarında tetiklenecek bir mekanizmaya ihtiyacımız var. İhtiyaçımıza proxy sınıfı cevap vermektedi. Proxy sınıfı içlerinde gerçek nesneyi barındıran gerçek nesneye yapılan her çağrıyı izleyen sınıflardır. Proxy içende barındırdığı nesneye bir çağrı geldiğinde Invoke fonksiyonu tetiklenmekte ve gerçek nesneye ait çağrı bu fonksiyon içinde yapılmaktadır. Proxy'ler yardımı ile nesneye gelen metot çağrılarını tek bir yerden kontrol edebiliriz. Fakat nesneye bir proxy oluşturabilmek için nesnelerimizi MarshalByRefObject sınıfından türetmeliyiz. Böylece proxy metot çağrılarını takip edebilecektir.
[cs]
public class PITest2 : MarshalByRefObject
{
[PerformanceCounterHandler ]
[ExceptionHandler ]
[CheckUserInRoleHandler ("Sales" )]
[CheckArgumentsHandler ]
public void SaveOrderToDB (Order order, User user)
{
Nesnemizi proxy oluşturabilecek duruma getirdik.
Sonraki problemimiz çapraz modülleri merkezi bir noktaya çekmektir. Çapraz modüllerin her birini bir poliçe haline getirebiliriz. Policeden kasıt çalıştığı an ki ortam bilgilerini toplayan ve tek bir atomik iş yapan uygulama bloğu sınıflarıdır. Örneğim hata yakalama işlemi için uygulamada bulunan tüm fonksiyonlarda çalışacak bir ExceptionHandlingManager yazabilirsiniz. Bu durumda her bir fonksiyonun içinde ExceptionHandlingManager sınıfınıza en az bir defa çağrıda bulunmanız gerkecektir. Yada hata kayalama işlemini yapan tek bir police yapar ve hata yakalama gereksimi olan fonksiyonların bu police ile çalışmasını sağlayabilirsiniz. Böylece hata yakalama sisteminizde bir değişiklik yapmak için sadece poliçenizi değiştirmeniz yeterli olacaktır. Ayrıca fonksiyonların içinde ki gereksiz kod bloklarından kurtulmuş olacaksınız.
[cs]
public class ExceptionHandlingCallHandler : CallHandler
{
public override ReturnMessage Invoke (IMethodCallMessage message, object realObject)
{
var result = NextHandler .Invoke (message, realObject);
if (result.Exception != null )
{
var errorMessage = string .Format ("{0}{1}{2}" , DateTime .Now , result.Exception .Message ,
result.Exception .StackTrace );
Console .WriteLine (errorMessage, ConsoleColor.Red, true );
Trace .TraceError (errorMessage);
}
return result;
}
}
Yukarıda örnek bir hata yakalama policesini görmektesiniz. Police sorumluluk zincirinde yer alan kendsinden sonra ki policeleri çağırmakta eğer bu zincir üzerinde bir hata oluşursa bunu ekrana basmaktadır. Şimdi nesnemizin nasıl kullanıldığına bakalım:
[cs]
PITest2 instance = PolicyInjector .CreateObject ();
var order = new Order {AccountId = 10, Unit = 3, UnitAmount = 5};
var user = new User {UserName = "saleperson1" , Roles = new [] {"Sales" }};
instance.SaveOrderToDB (order,user);
Kullanımda ki tek fark nesneyi doğrudan oluşturmak yerine PolicyInjector Factory sınıfı ile oluşturmamızdır.
Şimdi PolicyInjector sınıfını inceleyelim. Nesnelere poliçeleri enjekte edebilmek için nesnelerin tek bir merkezden üretilmesi gerekmektedir. PolicyInjector bizim yerimize sınıflarımızın proxy'lerini oluşturan, metot çağrılarında ki sorumluluk zincirini oluşturan ve metot çağrısı sırasında poliçeleri enjecte eden Factory sınıfımızdır. Yaptığı iş çok sihirlide olsa kendisi oldukça basit ve 150 satırlık bir sınıfdır.
[cs]
public class PolicyInjector : RealProxy where TObject : new ()
{
private readonly TObject _realObject ;
public PolicyInjector (TObject realObject)
: base (typeof (TObject ))
{
_realObject = realObject;
}
public static TObject CreateObject ()
{
var reelInstance = new TObject ();
return Wrap (reelInstance);
}
public static TObject Wrap (TObject reelInstance)
{
var proxyObject = new PolicyInjector (reelInstance);
var transparentProxyObject = (TObject )proxyObject.GetTransparentProxy ();
return transparentProxyObject;
}
public override IMessage Invoke (IMessage msg)
{
var message = (IMethodCallMessage )msg;
var chain = CreateChainOfResponsibility (message);
var result = chain.Invoke (message, _realObject );
return result;
}
private CallHandler CreateChainOfResponsibility (IMethodMessage message)
{
var chain = new List ();
AddToChainFromInterfaceHandlers (message, chain);
AddToChainFromClassHandlers (message, chain);
// add method call
chain.Add (new PolicyPear { CallHandler = new MethodCallHandler () });
// organize chain
for (var i = 1; i < chain.Count; i++)
{
chain[i-1].CallHandler.SetChain(next:chain[ i ].CallHandler,
handlerAttribute: chain[i - 1].HandlerAttribute );
}
return chain[0].CallHandler ;
}
private void AddToChainFromInterfaceHandlers (IMethodMessage message, ICollection chain)
{
var realType = _realObject .GetType ();
foreach (var attributes in
realType.GetInterfaces ()
.SelectMany (@interface => (
from method in realType.GetInterfaceMap (@interface).InterfaceMethods
where method.Name == message.MethodBase .Name
select method.GetCustomAttributes (typeof (CallHandlerAttribute ), true ))))
{
attributes.Cast ()
.OrderBy (c => c.CallHandlerType .MetadataToken ).ToList ()
.ForEach (attribute =>
chain.Add (new PolicyPear
{
CallHandler=Activator.CreateInstance(attribute.CallHandlerType) as CallHandler,
HandlerAttribute = attribute
})
);
}
}
private void AddToChainFromClassHandlers (IMethodMessage message, ICollection chain)
{
var realType = _realObject .GetType ();
var attributes =
realType.GetMethod (message.MethodName ).GetCustomAttributes (typeof (CallHandlerAttribute ), true );
if ((attributes == null ) || (attributes.Length <= 0)) return ;
bool needReserve = chain.Count == 0;
var list = attributes.Cast ()
.ToList ();
if (needReserve) list.Reverse ();
list.ForEach (attribute => chain.Add (new PolicyPear
{
CallHandler = Activator .CreateInstance (attribute.CallHandlerType ) as CallHandler ,
HandlerAttribute = attribute
}));
}
private sealed class MethodCallHandler : CallHandler
{
public override ReturnMessage Invoke (IMethodCallMessage message, object realObject)
{
var methodRetval = message.MethodBase .Invoke (realObject, message.InArgs );
return new ReturnMessage (methodRetval, null , 0, message.LogicalCallContext , message);
}
}
private sealed class PolicyPear
{
public CallHandler CallHandler { get ; set ; }
public CallHandlerAttribute HandlerAttribute { get ; set ; }
}
}
public abstract class CallHandler
{
internal void SetChain (CallHandler next, CallHandlerAttribute handlerAttribute)
{
NextHandler = next;
CallHandlerAttribute = handlerAttribute;
}
public CallHandler NextHandler { get ; private set ; }
protected CallHandlerAttribute CallHandlerAttribute { get ; private set ; }
public abstract ReturnMessage Invoke (IMethodCallMessage message, object realObject);
}
[AttributeUsage (AttributeTargets.Method )]
public class CallHandlerAttribute : Attribute
{
public CallHandlerAttribute (Type callHandlerType)
{
if (callHandlerType != null && !typeof (CallHandler ).IsAssignableFrom (callHandlerType))
throw new TypeLoadException (string .Format ("{0} is not assingable from {1}" , callHandlerType.Name ,
typeof (CallHandler ).Name ));
CallHandlerType = callHandlerType;
}
public Type CallHandlerType { get ; private set ; }
}
Tüm bu sihirli işleri yapan kodların hepsi bu kadar!
PolicyInjector yeni bir nesne oluştururken nesnenin proxy örneğini geri döndermektedir. Proxy örneği üzerinde yapılan tüm metot çağrıları PolicyInjector sınıfımızın Invoke metoduna düşmektedir. Invoke metotu içinde önce metotun poliçelerine göre bir sorumluluk zinciri oluşturulmaktadır. Sorumluluk zinciri oluşturulurken önce interface üzerinde ki metot tamına ait poliçeler zincire eklenmektedir. Daha sonra metota ait poliçelerde zincire eklenmektedir. Zincir oluşturulurken her bir poliçeye bir sonra ki poliçe atanmaktadır. Zincir oluşturulduktan sonra zincirin ilk elemanı çağrılmaktadır. Zincir üzerinde ki her bir police kendi işlevini yapmakta ve bir sonraki policeyi çağırmaktadır. Sorumluluk zincir üzerinde ilerlemekte ve en son metodu çağırmaktadır. Metot çalıştıktan sonra sorumluluk zincir üzerinde sondan başa doğru hareket etmekte ve PolicyInjector Invoke metotu içinde ki result değişkenine sonuç atanmaktadır. Invoke fonksiyonu da bu sonucu metodu çağıran koda dönmektedir.
Böylelikle 150 satırda projenizde ki karmaşayı ortadan kaldıraçak bir PolicyInjector sınıfına ulaşmış olduk. Policy Injection metotu ile çapraz modülleri policeler haline getirip çok ciddi bir kod temizliği ve yönetim kolaylığı sağlayabilirsiniz.
Yardımlarından dolayı Engin Özer Beye teşekkürler...
Hiç yorum yok:
Yorum Gönder