TeletAPI’nin API’si

Yazılımcılar detaylara olan sevdaları ile tanınırlar. Bir yazılımcı kullanılmak üzere yeni bir API (Application Programming Interface) geliştirdi ise ve kendisinden bu yeni API’nin nasıl kullanıldığının açıklanması istenirse, size detaylar içinde kaybolmanız ve oturum sonunda API’nin nasıl kullanıldığını anlamamanız garantisini veririm.

Bir API’yi kullanmak nedir, önce bunun tanımlamasını yaparak başlamak istiyorum. Bir API ideal bir dünyada bir yazılım modülünün, kendisinin dış dünyadan kullamını mümkün kılmak için dış dünyaya açtığı pencere ya da pencerelerdir. Biliçli olarak bu tanımlamada kapı kelimesini kullanmadım, çünkü pencereyi kullanarak modülün iç dünyasında olup, bitenleri anlamak mümkün değildir. Ama kapı metaforunda kapıyı kullanarak içeri girmek ve olup bitenleri görmek ve anlamak mümkündür. Bu demek oluyor ki bir API kesinlikle modül içinde olup bitenlerin dış dünyaya sızmasına izin vermez ya da vermemelidir. Modülün nasıl çalıştığını API’ye bakarak anlamak mümkün değildir. API sadece modül ile bir kara kutuymuşcasına interaksiyona girmek için kullanılır. Bu API aracılığı ile sadece modülün sunmak istediği servislerin, izin verdiği ve kendisinin tanımladığı şekilde kullanılabilmesi anlamına gelmektedir. Modülün bu servis ya da servisleri kendi iç dünyasında nasıl hazırladığını anlamak, görmek ya da değiştirmeye kalkmak API aracılığı ile mümkün değildir. Tabi modül buna yine API’si aracılığıyla izin veriyorsa, durum değişik olacaktır.

Bu tanımlamaya göre bir API kullanıcısı API’nin arkasındaki gizli dünyayı bir kara kutu olarak görür. Bu kara kutuyu programlamış programcı için ise durum farklıdır. Programcı tüm detayları bilir ve API’nin nasıl kullanıldığını tanıttığı bir oturumda bu detaylarda kaybolur gider.

Bunun başlıca sebebi yazılımcının kendi geliştirdiği modüle kullanıcı gözlüğünü takarak bakamamasıdır. O her zaman modülü bir beyaz kutu (white box) olarak görür. Bu gözlüğü takarak modülü geliştirir. Bu aslında bir noktaya kadar yapması gereken bir seydir, lakin belli zamanlarda kullanıcı gözlüğünü takabilmelidir. Takamadığı taktirde kullanıcının anlamakta ve kullanmakta güçlük çektiği API’ler oluşabilir.

Yazılımcı modül API’sini mutlaka kullanıcı gözlüğü ile tasarlamalıdır. Bunun için en ideal zaman modül için kod geliştirmeye başlamadan öncesidir. Ortada herhangi bir modül yokken, API’si vardır. Bu şekilde düşünmek bile birçok yazılımcıyı zorlar. Yazılımcı hemen oturur ve hayal ettiği şekilde modülü geliştirmeye başlar. Bu esnada kullanılmayan ya da gereksiz birçok metot ve sınıf oluşur. Bunun sebebi yazılımcının modülün nasıl kullanılacağını bilmemesinde yatmaktadır. En kötü ihtimalle yazılımcı modülün işleyiş tarzını kodladıktan sonra API’si hakkında kafa yormaya başlar. Bu API yırtık yamadan farksızdır ve modülün verimli bir şekilde kullanılmasının önünde engeldir. Bunun bir örneği aşağıda yer alan kodda yer almaktadır.

	File metaFile = new File("abc");
	Storage storage = StorageHelper.createStorage(StorageLoader.load(MetaHelper.createMetaFile(metaFile)));
	Reader reader = storage.getReader();
	Object obj = reader.getObjectById(1);
	

Bu kod parçasına ilk bakıldığında ne yaptığını anlamak zor olmamakla birlikte, kullanılması zor olan bir API’yi ihtiva etmektedir. Bu kod örneğinde Storage isminde bir modül mevcuttur. Kullanıcı bu modülü kullanabilmek için Storage, StorageHelper, StorageLoader, MetaHelper gibi aslında pek varlıklarından bile haberdar olmaması gereken sınıflarla boğuşmaktadır. Bunun sebebi bu modülün bir API’sinin olmaması ve geliştiricisinin geliştirme sürecinde bu modül nasıl en kolay bir şekilde kullanılır sorusuna cevap aramamış ya da bu soruya cevap bulamamış olmasıdır. Yazılımcı API zannettiği birçok sınıfı ortaya atmış, hem modülün basit bir API üzerinden kullanılmasını engellemiş, hem de modül icinde olup bitenleri herkesin göreceği şekilde açığa vurmuştur. Bu tarz bir modül ve API oluşturulması gereksiz bağımlılıkları beraberinde getireceğinden, kodun bakımını uzun vadede çıkmaza sokacaktır. Şöyle bir API işi çok daha kolay yapmaz mıydı?

	Storage storage = Storage.create("abc");
	Object obj = Storage.getObjectById(1);
	

ya da;

	Object obj = Storage.instance("abc").getObjectById(1);
	

Yukarıda yer alan örneklerde veri tabanından belli bir nesneyi edinmek için önce bir Storage nesnesinin oluşturulması ve akabinde getObjectById() metodun kullanılması yeterli olmaktadır. Modülün API’si sadece bu ya da buna benzer metotlardan oluşmaktadır. Kullanıcının diğer örnekte görüldüğü gibi modül içinde kullanılan StorageLoader ya da MetaHelper gibi sınıfları tanıması ve kullanması şart değildir. Bu modülün kullanımını kolaylaştıran ve API’yi sadeleştiren bir durumdur. Böyle bir API’de kullanıcı sadece Storage sınıfına bağımlı olduğundan, modülün içinde yer alan sınıflar bünyesideki herhangi bir değişiklik kullanıcıyı etkilemeyecektir. Verdiğim ilk örnekte modülün sahip olduğu iç sınıflara bağımlılık doğrudan olduğu için, modülü kullanan kodun kırılganlığı modül üzerinde yapılan her değişiklik ile doğru orantıda artacaktır.

API tasarımı modül için gerekli kodun yazılmasından sonrasına bırakılamayacak kadar ciddi bir konudur. Öyle ise API tasarımını kod yazmadan destekleyecek bir yöntemin kullanılmasında fayda vardır. Bu yöntemin ismi test güdümlü yazılımdır (TDD – Test Driven Development).

Test güdümlü yazılımın en büyük avantajlarından birisi kod yazmadan, geliştirilmek istenen kod biriminin kullanıcı gözünden görülmesini sağlamasıdır, çünkü yazılan tesler oluşturulan kod birimleri için kullanıcı olma niteliğindedir. Yani aslında test sınıfları API’lere yönlendirilmiş kullanıcılardır. Bu sebepten dolayı testler bir kullanıcının ihtiyaçları doğrultusunda kod birimlerini test ederler. Durum böyle olunca test güdümlü yazılım yaparken oluşturlan testlerde akla gelen ilk soru “ihtiyacım nedir ve API bu ihtiyacımı nasıl giderir” şeklinde olmaktadır. API yoksa oluşturulur, varsa doğrudan kullanılır.

Şimdi modül için gerekli kodu yazmadan testler aracılığı ile ihtiyaç duyduğumuz Storage API’sini nasıl oluşturabileceğimizi bir örnek üzerinde inceleyelim. Testler ile birlikte API ve akabinde modül için gerekli kod yavaş yavaş oluşmaya başlayacaktır. İhtiyaç duyduğumuz API’yi aşağıdaki şekilde test ederek başladığımızı farz edelim. Ortada henüz Storage isminde bir sınıf yok, dolayısıyla bu sınıfın getObjectById() isminde bir metodu da bulunmuyor. Ama biz ihtiyaç duyduğumuz API’yi hayal ederek, yani kullanıcı olarak yola çıkarak böyle bir sınıf ve böyle bir metot olsaydı, istediğimiz veriyi veri tabanından böyle edinebilirdik şeklinde hayal ettik ve bunu da test olarak ifade ettik.

	@Test
	public void 
	storage_should_deliver_the_value_1(){
		Storage storage = new Storage("abc");
		Object obj = storage.getObjectById(1);
		assertThat(obj.getValue(), equalTo(1));
	}
	

Oluşturduğumuz test bizi ister, istemez sade bir API oluşturmaya itmektedir. Bu tarz bir test yazmak bizi kesinlikle önce bir MetaHelper sınıfı, akabinde bir StorageLoader ve daha sonra veri tabanını kullanıma hazırlamak için kullanılan StorageHelper sınıfını oluşturmaya yönlendirmemektedir, çünkü kullanıcı olarak bunlar bizi ilgilendirmeyen, modülün kendi sorumluluğunda olan konulardır. Bizim kullanıcı olarak tek bir beklentimiz vardır, o da API üzerinden belli bir verinin veri tabanından en kolay şekilde nasıl edilenilebileceği konusudur.

Testi oluştururken taktığımız kullanıcı gözlüğü bizi her zaman kullanıcının gereksinimlerini doğrudan tatmin eden bir API’yi oluşturmaya yönlendirmektedir. Kullanıcı gözlüğü modül içinde olup, bitenler ile ilgilenmemektedir, çünkü detayları bilmesi verdiğim ilk örnekteki gibi kafa karıştırıcı tarzda olacaktır. Bu yüzden kullanıcı her zaman en sade API’yi tercih etme iç güdüsüne sahiptir.

Yazmış olduğum testin başarılı olması için Storage isminde bir sınıfın oluşturulması ve bu sınıfın getObjectById() isminde bir metodunun olması gerekmektedir. Bu noktadan itibaren detayları bilen yazılımcı gözlüğünü takarak modülü ve içeriğini düşündügüm şekilde geliştirebilirim. Böyle bir API oluştuktan sonra yazılımcı iç güdüsel olarak Storage modülünün tüm işleyiş tarzını kullanıcıdan saklamaya özen gösterecektir, çünkü kullanıcının getObjectById() metodu haricinde başka bir şeye ihtiyacı olmadığını bilmektedir. Bunu kendisine söyleyen yazdığı birim testidir. Birim testi modülün kullanılma şekillerinden birisini yazılımcıya göstermiştir. Storage modülü için yapılabilecek implementasyonlardan birisi şu şekilde olabilirdi.

	
	public class Storage{

	private static Storage storage;	

	private Reader reader;

	public static Storage create(String dir){
		if(storage == null){
			storage = new Storage(dir);
		}
		return storage;
	}
		
	private Storage(String dir){
		initStorage(dir);
	}

	private void initStorage(String dir){
		File metaFile = new File(dir);
		reader = StorageHelper.createStorage(StorageLoader.load(MetaHelper.createMetaFile(metaFile))).getReader();
	}

	public Object getObjectById(int i){
		return reader.getObjectById(i);
	}

	class Reader{
	...
	}
	
	class StorageHelper{
	...
	}

	class StorageLoader{
	...
	}

	class MetaHelper{
	...
	}

	

Storage modülünü bir singleton olarak implemente ettim. Görüldüğü gibi Storage.create() statik metodu ile singleton olan bir storage nesnesi edinmek mümkündür. Akabinde bu nesneyi kullanarak getObjectById() metodu aracılığı ile veri tabanından istediğim türde bir veriyi çekebilmekteyim. Bunun yanısıra modül bünyesinde kullanılan tüm sınıfları iç sınıf olarak tanımladım. Bu şekilde dış dünyadan hiç kimse bir Reader ya da StorageHelper nesnesi oluşturamaz ya da kullanamaz. Storage modülü bu şekilde iç dünyasında olup bitenleri tamamen dış dünyadan gizlemekte ve tanımladığı iki metot aracılığı ile kullanımını mümkün kılmaktadır. Bu iki metot Storage modülünün API’sidir. Storage modülünün kara bir kutu gibi işlev görmesi, kullanıcıları etkilemeden modül bünyesinde değişiklik yapılabilmesini mümkün kılmaktadır.

Yapmış olduğumuz API tanımlamasına “API kullanıcı ve sunucu arasında bir kullanım sözleşmesidir” ibaresini ekleyebiliriz. Nitekim bir modül sahip olduğu API’si aracılığıyla dış dünyaya nasıl kullanılabileceğinin mesajını verir. Kullanıcılar istedikleri API metotlarını seçerek modülü kullanmaya başlarlar. Bu kullanıcı ve modül arasında bir bağ oluşturur. Kullanıcı her zaman oluşan bu bağın sağlam olmasını arzular. Değişikliğe uğrayan API kullanıcılarını kırılgan hale getirir. Bunu engellemek için API’nin sıkça değişmemesi şarttır. Durum böyle olunca bir defa kullanıma sunulan bir API’nin değiştirilmesi artık kolay değildir, çünkü sayısı bilinmeyen birçok kullanıcısı vardır. Bunu göz önünde bulundurduğumuzda API tasarımcısı olarak her zaman tutucu (konservatif) bir pozisyonda durarak oluşturduğumuz sınıfları öncelikle iç sınıf ya da package private olarak tanımlamamız gerekir. Sadece bu durumda hemen keşfedilerek kullanılmalarını engelleyebiliriz. Kullanımını engelleyemediğimiz sınıflardan her zaman sorumluyuzdur. Bir API kullanıma açıldığında o API’de ne kadar sınıf ve metot mevcutsa, modülün ömrü boyunca bu sınıflara ve metotlara destek vermek zorunda kalırız, çünkü kullanıcılarını API’yi değiştirerek sinirlendirmemiz gerekir. Bu yüzden API’nin çapı ne kadar küçükse, sorumluluk ve verdiğimiz desteğin oranı o oranda küçük olacaktır.

API’lerin kullanıcılara hitap edecek ve onların gereksinimleri doğrudan ve kolay bir şekilde tatmin edecek şekilde tasarlanmaları şarttır. Windows ortamında C/C++ ile Windows API’sini kullanarak uygulama geliştirenler çok iyi bilirler. Bu API en zor API’lerden bir tanesidir. API sadece birkaç sınıf ya da metot birleşimi bir sey değildir. Aynı zamanda API’nin ihtiva ettiği sınıf ve metot isimlerinin ifade gücünün yüksek olması gerekir. Her sınıf ya da metot için seçilen isim bu API’nin arkasındaki gizli servisin nasıl kullanılabileceğini ifade edebilmelidir. Unutmayalım programlar bilgisayarlar için yazılıyor, lakin kodu okuyan insanlar. Bu yüzden ifade gücü yüksek olan isimlerin seçilmesi API’nin doğru kullanımı kolaylaştıracaktır.

Bol TeletAPI’li günler diliyorum.


EOF (End Of Fun)
Özcan Acar

TeletAPI’nin API’si” hakkında 4 yorum

  1. gturedi

    Güzel bir makale olmuş hocam, inşallah bize de, gün gelir api tasarımı sorumluğu verilir, biz de belirttiğiniz noktaları dikkate alıp bu önemli görevi layıkıyla yerine getiririz.

  2. gturedi

    Hocam tdd sürecinde kod yazarken dedik ki olabildiğince sade ve kullanımı kolay olan doğal bir api ortaya çıkıyor. Fakat metotların test edilebilmesi için public olmaları zorunluluğu şu durumlar da api’nin sadeliğine karşı olmuyor mu:
    örneğin x sınıfı içindeki a metodu x’in dışa açılan yüzü fakat işlevini yerine getirmesi için b ve c metotlarını da
    kullanıyor. Bu durumda normalde private olarak tanımlayacağımız b ve c metotlarını test edilebilirlik adına publicolarak dışa açtığımız noktada artık x sınıfının api’si kullanıcı sınıflar için daha karmaşık ve kalabalık hale gelmiş olmayacak mı?

  3. Özcan Acar Yazar

    TDD de oraya cikan genelde sistem API’si degil. Bir sistem API’si örnegi REST üzerinde baska bir sisteme entegre olmak icin bir interface sinif tanimlamak gibi bir sey. TDD’de adi gecen API daha cok siniflarin yapisi, disariya sunduklari hizmetler, yani sahip olduklari metotlar. Tabi TDD’de sistem API’leri tanimlanarak gelistirme yapilabilir, bunun önünde bir engel yok.

    >örneğin x sınıfı içindeki a metodu x’in dışa açılan yüzü fakat işlevini yerine getirmesi için b ve c metotlarını da
    kullanıyor. Bu durumda normalde private olarak tanımlayacağımız b ve c metotlarını test edilebilirlik adına publicolarak dışa açtığımız noktada artık x sınıfının api’si kullanıcı sınıflar için daha karmaşık ve kalabalık hale gelmiş olmayacak mı?

    Kullanici eger interface üzerinde gidiyorsa, zaten b ve c metotlarini görmeyecektir. Bu metotlar package private olarak tanimlanip, test edilebilir, illa public olmalari gerekmiyor.

Yorumlar kapalı.