Tasarım Kalıpları: Proxy (Vekil) – III

Proxy tasarım kalıbıyla ilgili bir önceki yazıyı, “Proxy kalıbını kullanabileceğiniz ya da kullandığınız ne gibi durumlar olabilir ya da oldu?” şeklinde bir soruyla kapatmıştık. Bu kalıbın kullanılabileceği durumlara bir örnek daha verelim.

Eskiden, örneğin 80’li yıllarda ya da 90lı yılların başında, webin yeni yeni çıktığı dönemlerde kulandığımız bilgisayarlar, gateway denen ağ yapıları üzerinden internete bağlanırlar ve ihtiyacınıza göre http, ftp, telnet vb. protokolları kullanarak haberleşirlerdi. Dolayısıyla gatewaylerin arayüzlerinde, bu protokollarla ilgili istekleri karşılayan yapılar olurdu. (Bu anlamda gatewayleri, üzerinde protokollere karşılık gelen metotlara sahip sınıf olarak düşünebilirsiniz.) Bir müddet sonra bu durumun sakıncaları ortaya çıkmaya başladı ve internete çıkışın kontrol edilmesi gerekti. Örneğin belli protokolleri kullanmak, belli sitelere ulaşmak vb. konularında kısıtlamalara gidilmek istendi. Ya da yazışmaların filtrelenmesi ve belli anahtar kelimelerin olduğu yazışmaların engellenmesi gibi ihtiyaçlar ortaya çıktı. Fakat tüm bu kontrollerin var olan bilgisayarlarda, gatewaylerde ve protokollerde olabildiğince hiç bir değişikliğe gerek duyurmaması da ayrıca istendi. Aksi taktirde bu kontrollerin maliyeti çok yüksek olurdu. Örneğin makinalardaki tarayıcılar http ve https ile web ortamına ulaşıyorlarsa, bu kontrollerin olduğu durumda da aynı şekilde davranmalıydılar. Kontroller ise araya girerek çalışmalı ve duruma göre, haberleşmenin devam etmesine izin vermeliydiler. Bu şu anlama gelmekteydi: Bilgisayarlar hala sanki gateway ile haberleşiyormuş gibi davranmaya devam etmeliler ama gerçekte gateway ile değil de araya giren ve arayüzü tamamen gatewayler gibi olan ve kendilerine “proxy” denen, gerekli kontrol ve filtreleme işlerini yapan yapılar ile haberleşmeliler. Proxyler, kontrol ve filtrelerden sonra ya haberleşmeyi “buraya erişmeye yetkiniz yoktur” ya da “yasaklı site!” gibi bir uyarılarla kesmeliler ya da probem olmadığı durumlarda haberleşmeye izin vermeliler. Bu şekilde, makinalarla internet çıkışı sağlayan gatewayler arasına girerek bu tip kontroller yapan yapılara “proxy” denmesinin sebebi, bu yapıların tam da bu kalıpta olduğu gibi, “vekil” olarak davranmalarıdır. Bu tür vekillerin en temel iki özelliğini hızlıca tekrar edelim:

  • Gerçekte ulaşılmak istenen nesneyi saklamak ama bunu, saklanan nesne ile aynı arayüze sahip olarak yapmak ve bu şekilde “yokmuş gibi” davranmak. Bu durum, saklanan nesneye ulaşmak isteyen, onu bilen ve onunla haberleşebilen yapılarda (önceki örneğimizde vatandaş, bu örnekte ise bilgisayar) herhangi bir değişiklik yapmadan, araya bu tür vekiller sokmanızı sağlar. Çünkü vekiller “yokmuş gibi” (transparent) davranırlar.
  • Gerekli kontrol ve filtreleme işlerini yapmak. Vekiller bu işleri, yokmuş gibi davranarak yaparlar. Ben ilk bu tür proxylerin kullanıldığı ağ ortamların çalışmaya başladığımda, ftp, telnet, http vb. protokollerle örneğin tarayıcılar üzerinden çalışırken, daha önceki hale göre oluşan gecikmelerden, “araya proxy konmuş!”  yorumunu yapardım. Çünkü proxynin varlığını başka türlü anlamayabilirsiniz. (Gerçi sonraları tarayıcılarda proxy ayarları vb. ek işlerle proxyler hayatımızda fark edilmez olmaktan çıktılar.)

Şöyle bir soru sorulabilir: Neden kontrolleri gatewaylere koymuyoruz ki? Bu soru ile önceki yazıda ele aldığımız vatandaş-başbakan örneğindeki “neden bu kontrolleri başbakana koymuyoruz” sorusunun cevabı aynıdır: “Çünkü bu tür kontroller onların görevleri değil ki!” Düşünün bazı büyük markalar yogun ağ trafiğini etkin bir şekilde yönetmek için çok yetkin router, gateway vs. gibi, uzmanı olmadığım, tonla ağ yapısını hem donanım hem yazılım olarak yöneten yapılar üretiliyorlar. Bu yapıların temel görevi, http, ftp, telnet vb. protokolları gerçekleştirmek. Bu protokollarla ilgili, performans, ölçeklenirlik, uyumluluk vb. konularda envai çeşit detay var ve sonuçta zaten çok karmaşık yapılar ortaya çıkıyor. Bu yapılara, güvenlik kontrolleri ve filtreleme gibi farklı konularla ilgili yine son derece karmaşık ve özellikle de sıklıkla değişme eğiliminde olan yetkinlikleri koymak çok da tercihe dilecek bir durum değildir. Aynı şekide bu iki sorumluluk alanını birleştirmek, başbakanın önüne görüşülecek binelrce vatandaş listesini koymaktan farklı bir durum değildir. Fiziksel dünyada bu gibi durumlarda daima sorumlulukları ayırmayı tercihe diyorsak, “separation of concern” ya da “single responsibility” gibi prensipleri uyguluyorsak, soyut ve bundan dolayı anlaşılması ve yönetilmesi daha zor olan yazılım yapılarında bu prensipleri haydi haydi uygulamalıyız. Aki taktirde ne karmaşıklığı ne de değişimi yönetebiliriz. Aksi taktirde yazdığımız sınıflar da metotlar da devasa olur ve iki günde yazdığımız kodu değiştirmek bir haftayı alır. İşte bu yüzden tasarım kalıplarını bilmek ve uygulamak bize çok güzel bir sorumlulukları ayırma ve değişimi yönetecek şekilde nesnelere atama yeteneği kazandırır.

Tekrar esas problemimize dönersek, gateway, proxy ve bilgisayar arasındaki ilişkiyi proxy tasarım kalıbıyla modellersek örnek olarak şöyle bir yapıya ulaşabiliriz:

Network, internete açılacak yapıların arayüzüdür:

package org.javaturk.dp.pattern.gof.structural.proxy.network;

public interface Network {
	public void telnet(String ip, String targetIp) throws YasakKardesimException;
	public void ftp(String ip, String targetIp) throws YasakKardesimException;
}

Gateway, gerekte internet bağlantısı yapan yapıdır. Koddan da görüldüğü gibi Gateway, bir Network‘tür ve singletondır.

package org.javaturk.dp.pattern.gof.structural.proxy.network;

public class Gateway implements Network {
	private static Gateway gateway = new Gateway();

	public void ftp(String ip, String targetIp) {
		System.out.println(ip + " makes an ftp to " + targetIp + "\n");
	}

	public void telnet(String ip, String targetIp) {
		System.out.println(ip + " makes a telnet to " + targetIp + "\n");
	}
	
	public static Gateway getInstance(){
		return gateway;
	}
}

 

ProxyServer ise, bilgisayar ya da bu örnekte NetworkClient ile Gateway arasına giren proxy yani vekil nesnesidir. Bu yüzden ProxyServer, bir Network‘tür ve internete gerçek çıkışı sağlayan Gateway‘in tek olan nesnesine sahiptir:

package org.javaturk.dp.pattern.gof.structural.proxy.network;

public class ProxyServer implements Network {
	
	private Gateway gateway;
	
	public ProxyServer(){
		gateway = Gateway.getInstance();
	}
	
	public void ftp(String ip, String targetIp) throws YasakKardesimException{
		Logger.log(ip + ", " + targetIp + " adresine ftp yapmak istiyor");
		
		if(targetIp.startsWith("192"))
			throw new YasakKardesimException(targetIp + " adresine ftp yapmaniz yasaktir!");
		
		gateway.ftp(ip, targetIp);
	}

	public void telnet(String ip, String targetIp) throws YasakKardesimException{
		Logger.log(ip + ", " + targetIp + " adresine telnet yapmak istiyor");
		
		if(targetIp.startsWith("192"))
			throw new YasakKardesimException(targetIp + " adresine telnet yapmaniz yasaktir!");
		
		gateway.telnet(ip, targetIp);
	}
}

 

ProxyServer‘ın kullandığı Logger ise basit loglama yapan bir sınıftır:

package org.javaturk.dp.pattern.gof.structural.proxy.network;

import java.util.Date;

public class Logger {
	public static void log(String message){
		System.out.println(new Date() + ": " + message);
	}
}

 

NetworkServer ise bilgiayarları (ya da Network Client), ağ altyapısına bağlayan sınıftır:

package org.javaturk.dp.pattern.gof.structural.proxy.network;

public class NetworkServer {
	
	private static NetworkServer ns = new NetworkServer();
	private Network network;
	
	private NetworkServer(){
		network = new ProxyServer();
	}
	
	public Network getNetwork(){
		return network;
	}
	
	public static NetworkServer getInstance(){
		return ns;
	}
}

 

Pek tabi ki kodumuzun tam olması için YasakKardesimException isimli sıra dışı durum sınıfına da ihtiyacımız vardır:

package org.javaturk.dp.pattern.gof.structural.proxy.network;

public class YasakKardesimException extends Exception{
	
	public YasakKardesimException(String message){
		super(message);
	}
}

 

Bilgisayar gibi ağ hizmetlerini tüketen nesne ise bu örnekte NetworkClient‘dır. Bu sınfı aynı zamanda main metoda da sahiptir.

package org.javaturk.dp.pattern.gof.structural.proxy.network;

public class NetworkClient {

	public static void main(String[] args) {
		NetworkServer networkServer = NetworkServer.getInstance();
		
		Network network = networkServer.getNetwork();

		String myIp = "10.0.0.2";
		try {
			network.telnet(myIp, "88.168.2.200");
		} catch (YasakKardesimException e) {
			System.out.println(e.getMessage());
		}

		try {
			network.ftp(myIp, "192.168.2.200");
		} catch (YasakKardesimException e) {
			System.out.println(e.getMessage());
		}
	}
}

 

NetworkClient sınıfını çalıştırdığımzıda şı çıktıyı elde ederiz:

Wed Feb 11 14:01:23 EET 2015: 10.0.0.2, 88.168.2.200 adresine telnet yapmak istiyor
10.0.0.2 makes a telnet to 88.168.2.200

Wed Feb 11 14:01:23 EET 2015: 10.0.0.2, 192.168.2.200 adresine ftp yapmak istiyor
192.168.2.200 adresine ftp yapmanız yasaktır!

 

Bu yazıda hem  proxy kalıbının güzel bir örneğini gördük hem de hem genelde tasarım kalıplarının hem de özelde proxy kalıbının, sorumlulukları doğru nesnelere atamakta ve aralarını değişimi gözeterek ayırmakta bize nasıl yardımcı olduklarını gözlemledik.

 

Toplam görüntülenme sayısı: 1723