Sunday, February 8, 2015

MongoDB ile Çalışmak



Klasik web uygulamaları, çok katmanlı mimari olarak tasarlanırlar. Her bir katmanın bir sorumluluğu vardır. Çok katmanlı yapıdaki her bir katman, kendinden önceki katmana hizmet verirken, kendinden sonraki katmandan hizmet alır. Şekil-1'de bu katmanlı yapıda yer alan 5 katman tanıtılmıştır:
  • İ: İstemci katmanı. Çoğu zaman bir web tarayıcısıdır.
  • S: Sunum katmanı. Uygulamanın kullanıcıya dönük yüzünü oluşturmaktan sorumludur. Sunum mantığını içerir.
  • İM: İş Mantığı katmanı. İşin asıl yapıldığı yerdir.
  • T: Tümleştirme katmanı. Kurumsal uygulamalar geliştirilirken çoğu zaman kurum içindeki ya da dışındaki diğer yazılımlarla etkileşim kurması istenir. Tümleştirme katmanı bu iletişimin sorunsuz gerçekleşmesinden sorumludur.
  • K: Kaynak katmanı. Verilerin kalıcı olarak saklandığı katmandır. Veritabanı (Oracle, MySQL, DB2 gibi), Dizin sunucusu (Active Directory, OpenLDAP), CRM (Oracle Siebel, Microsoft Dynamics), ERP uygulaması gibi çok farklı türde olabilir.

Şekil-1: Klasik web mimarisi

İstemci ile sunucu arasındaki etkileşim istek-cevap örüntüsündedir. Uygulamanın arayüzünün değişmesi için mutlaka sunucuya bir isteğin gitmesi gerekir. Sunum mantığı da sunucuda yer alır. Bu durum mimarinin zayıf halkasını oluşturur. Sunumdaki her değişiklik mutlaka sunucuya uğramak zorundadır. Sunumdaki bu problemin, Ajax ile bir ölçüde gizlendiği söylenebilir. Ajax teknolojisi, istemcilerin isteklerini asenkron bir şekilde sunucuya iletmelerine olanak sağlamıştır. Ajax istek cevabı şimdi sadece arayüzdeki değişiklikleri içerebilmektedir. Bu sunucunun yükünü bir miktar azaltır.
Kaynak katmanında çoğu zaman ilişkisel veri tabanları kullanılır. İlişkisel veri tabanları, ilişki cebri üzerine kurulmuştur. 1970'li yıllardan beri geliştirilen, sınanmış olgun bir teknolojidir. Ama iş ölçeklenebilirliğe geldiğinde, doğası gereği, başarılı oldukları söylenemez. Her kurumsal uygulama, ilişkisel veri tabanına ihtiyaç duyar. Dolayısı ile veritabanları her zaman potansiyel bir dar boğaz noktasıdır. 
Web tarayıcıları, HTML 5 ile birlikte gelen API'ler yardımı ile üzerinde uygulama koşturabileceğimiz platformlara dönüştüler:
  • Canvas API, 
  • Data Storage API, 
  • Drag and Drop API, 
  • Messaging and Workers API, 
  • WebSocket API, 
  • Geolocation API
Artık WebGL yardımı ile doğrudan grafik kartı üzerinde işlem yapabiliyoruz, web tarayıcısında yerel bir veri tabanı üzerinde çalışabiliyoruz. Tüm bu API'ler için programlama dili ise JavaScript (JS). Web tarayıcıları, uzun bir süredir, JS'de yazılmış kodları çok hızlı çalıştırabilen JS motorlarına sahipler. Bu JS motorları, Java Sanal Makinasının kullandığı ihtiyaç anında derleme gibi kodun hızlı çalışmasını sağlayacak teknikleri uygulamaktalar. İstemcileri makinalarında bugün en az iki çekirdekli işlemciler bulunuyor, ekran kartları en kötü HD filmleri izleyebilecek, orta karmaşıklıktaki oyunları 30 fps'de oynatabilecek ana kart üzerine gömülü grafik modüllerine sahipler. Makinaların en kötü 4GB bellekleri ve çok miktarda depolama ortamları bulunuyor. İnternet ağı bant genişlikleri HD filmleri takılmadan izlememize imkan sağlıyor. Tüm bu gelişmeler, artık klasik web uygulamalarını terk etmemizi gerektiriyor. Yenil nesil uygulamaların ihtiyaçları farklı:
  • Daha çok kullanıcı etkileşimi, 
  • Daha zengin kullanıcı deneyimi
  • Mobil cihazlardan, hareket halindeyken erişim
  • Değişen bant genişliklerinde çalışabilme
  • Bağlantının kesildiği durumlarda da çalışabilmek
  • Süper ölçeklenebilirlik
Bu ihtiyaçları karşılamak üzere yeni web mimarilerinin geliştirildiğini görüyoruz. Klasik web uygulamalarının aksine bu yeni mimarilerde sunum ve sunum mantığının sunucudan istemciye taşındığını görüyoruz (Şekil-2):
Şekil-2: İnce sunucu-donatımlı istemci modeli

Bu yeni mimaride sunucunun inceldiğini görüyoruz. İstemcilerin artık daha seyrek olarak ve daha çok veriye erişmek için sunucuya gittiklerini görüyoruz. İstemci tarafta programlama dili JS olduğu için veriye erişirken, en verimli kodlama biçimi Java Script Object Notation (JSON) olacaktır. HTTP protokolü üzerinden eriştiğimiz sunucuda, veri servisinin, kabul görmüş RESTful servis olarak tasarlandığını görüyoruz. RESTful servisler HTTP protokolünü kullanır. HTTP protokolü belleksiz yalın bir mantığa sahiptir. Dolayısı ile son derece ölçeklenebilir bir çözüm sunar. Tüm bu mimari değişimde, veri tabanı olarak sadece ilişkisel veri tabanı kullanamayız. Bu süreçte, ilişkisel veritabanları ile birlikte kullanılacak, yeni doğan ihtiyaçlara yanıt verebilecek yeni veritabanlarının türediği görüyoruz. Bu veri tabanlarına toplu olarak "Not Only SQL" anlamında NoSQL adını veriyoruz.  
Şekil-3: Süper ölçeklenebilir model
Farklı türlerde NoSQL veri tabanları bulunuyor:
Listenin bununla sınırlı olduğunu düşünmeyin. Bu yazıda JSON dokümanlarını saklayan bir NoSQL çözümü inceleyeceğiz: MongoDB. Kurulumunu ve yapılandırılmasını ele alacağız. Ayrıca JS kullanarak nasıl sorgu çalıştırılacağını çalışacağız.

Kurulum


MongoDB kurulum dosyasını bu bağlantıdan elinizdeki işletim sistemi için indirmeniz gerekiyor. Windows, Linux, Solaris ve MacOS platformları için MongoDB kurulum dosyası bulabilirsiniz. Açık kaynak kodlu bir proje olan MongnDB, C++'da yazılmıştır. Kaynak kodlara bu bağlantıdan erişebilirsiniz.
MongoDB kurulumu indirdiğiniz zip dosyasını uygun bir dizine açarak tamamlanıyor. Bu yazı için kurulumu Windows 8.1 x64 platformunda gerçekleştirdim. mongodb-win32-x86_64-2008plus-ssl-v3.4-latest.zip dosyasını c:\opt64 dizinine açtım ve dizini mongodb-3.4 olarak isimlendirdim. Tüm çalıştırılabilir dosyalar bin dizini içinde bulunuyor:
  • mongo.exe: Komut satırı istemcisi
  • mongod.exe: Sunucu yazılım
  • mongodump.exe: Yedek almak için kullanılan yazılım. Yedekleme dosyaları bson formatındadır.
  • bsondump.exe: bson formatındaki dosyaları komut satırından okumak için kullanılır.
  • mongoexport.exe: MongoDB sunucuları arasında veri alış verişi için kullanılır.
  • mongofiles.exe
  • mongoimport.exe: MongoDB sunucuları arasında veri alış verişi için kullanılır. Bir örneğini yazının devamında bulabilirsiniz.
  • mongooplog.exe
  • mongoperf.exe
  • mongorestore.exe: mongodump ile alınan bütün bir yedekten geri dönmek için kullanılır
  • mongos.exe
  • mongostat.exe: Unix'deki vmstat komutuna benzer bir işleve sahiptir. mongod sunucunun ekleme/silme/güncelleme başarımını izlemenize olanak sağlar:
mongostat
connected to: 127.0.0.1
insert  query update delete getmore command flushes mapped  vsize    res faults  locked db idx miss %     qr|qw   ar|aw  netIn netOut  conn       time
    *0     *0     *0     *0       0     1|0       0   320m  2.91g    80m      0 world:0.0%          0       0|0     0|0    62b     3k     1   01:32:24
    *0     *0     *0     *0       0     1|0       0   320m  2.91g    80m      0 world:0.0%          0       0|0     0|0    62b     3k     1   01:32:25
    *0     *0     *0     *0       0     1|0       0   320m  2.91g    80m      0 world:0.0%          0       0|0     0|0    62b     3k     1   01:32:26
    *0     *0     *0     *0       0     1|0       0   320m  2.91g    80m      0 world:0.0%          0       0|0     0|0    62b     3k     1   01:32:27
    *0     *0     *0     *0       0     1|0       0   320m  2.91g    80m      0 world:0.0%          0       0|0     0|0    62b     3k     1   01:32:28
    *0     *0     *0     *0       0     1|0       0   320m  2.91g    80m      0 world:0.0%          0       0|0     0|0    62b     3k     1   01:32:29
    *0     *0     *0     *0       0     1|0       0   320m  2.91g    80m      0 admin:0.0%          0       0|0     0|0    62b     3k     1   01:32:30
    *0     *0     *0     *0       0     1|0       0   320m  2.91g    80m      0 world:0.0%          0       0|0     0|0    62b     3k     1   01:32:31
    *0     *0     *0     *0       0     1|0       0   320m  2.91g    80m      0 world:0.0%          0       0|0     0|0    62b     3k     1   01:32:32
  • mongotop.exe: Unix'deki top komutuna benzer bir işleve sahiptir. Bu kez mongod yönetimindeki torbaların kullanım istatistiklerini listeliyor:
mongotop
connected to: 127.0.0.1

                            ns       total        read       write
       world.system.namespaces         0ms         0ms         0ms
               world.system.js         0ms         0ms         0ms
          world.system.indexes         0ms         0ms         0ms
                 world.movies1         0ms         0ms         0ms
              world.countries2         0ms         0ms         0ms
              world.countries1         0ms         0ms         0ms
                 world.cities2         0ms         0ms         0ms
          local.system.replset         0ms         0ms         0ms

MongoDB istemci-sunucu mimarisine sahiptir. Sunucuyu başlatmak için mongod.exe programını kullanıyoruz. Sunucuya komut satırından bağlanmak için ise mongo.exe programını kullanıyoruz. MongoDB JSON dokümanlarını hızlı erişim için bellekte, kalıcı hale getirmek için ise BSON (Binary JSON) olarak diskte saklar. BSON, JSON formatındaki dokümanları bilgisayar ağı üzerinden etkin aktarımı için tasarlanmış bir formattır. Formatın detaylarına bu bağlantıdan erişebilirsiniz.
Sunucuyu komut satırından çalıştırmak için aşağıdaki komutları bir betik haline getirip çalıştırıyoruz:
SET MONGODB_HOME=c:\opt64\mongodb-3.4
SET PATH=%MONGODB_HOME%\bin;%PATH%
start mongod --dbpath=%MONGODB_HOME%\data\db

Sunucunun ekran çıktısı aşağıda verilmiştir:

Windows servise eklemek için Administrator yetkisi ile aşağıdaki komutları çalıştırmak gerekir:
mkdir c:\opt64\mongodb-3.4\log
echo logpath=c:\opt64\mongodb-3.4\log\mongolog > mongo.cfg
set PATH=c:\opt64\mongodb-3.4\bin;%PATH%
cd c:\opt64\mongodb-3.4
mongod --install --config c:\opt64\mongodb-3.4\mongo.cfg --dbpath c:\opt64\mongodb-3.4\data\db

Windows servise MongoDB adıyla eklenen sunucuyu başlatmak ve kapatmak için ne komutu yeterlidir:
net start MongoDB
net stop MongoDB

Bundan sonraki örneklerde kullanmak üzere iki tane veri tabanını yükleyeceğiz. Bunun için mongoimport yazılımını kullanacağız. Örnek veri tabanlarını bu bağlantıdan indirip, örneğin c:\tmp dizinine açalım. İçinden çok sayıda json uzantılı dosya çıkacaktır: 
  • countries_v1.json
  • countries_v2.json
  • cities_v2.json
  • directors_v2.json
  • genres_v2.json
  • movies_v1.json
  • movies_v2.json
Bu dosyaları mongoimport yazılımını kullanarak veri tabanına geri yükleyelim:
mongoimport  --db imdb --collection movies1 --quiet --file c:\tmp\movies_v1.json --jsonArray
mongoimport  --db imdb --collection movies2 --quiet --file c:\tmp\movies_v2.json --jsonArray
mongoimport  --db imdb --collection directors2 --quiet --file c:\tmp\directors_v2.json --jsonArray
mongoimport  --db imdb --collection genres2 --quiet --file c:\tmp\genres_v2.json --jsonArray

mongoimport  --db world --collection countries1 --quiet --file c:\tmp\countries_v1.json --jsonArray
mongoimport  --db world --collection countries2 --quiet --file c:\tmp\countries_v2.json --jsonArray
mongoimport  --db world --collection cities2 --quiet --file c:\tmp\cities_v2.json --jsonArray

İlişkisel veri tabanlarında verileri tablolarda kayıtlar olarak saklıyoruz. MongoDB'de ilişkisel veri tabanlarındaki tablolara karşılık olarak torbalar (=Collection) yer alır. Tablolardaki kayıtlara karşılık olarak torbalarda, json dokümanlar yer alır (Şekil-4). İlişkisel veri tabanlarında, normalizasyon kurallarına uygun olarak, veri tutarlığını sağlamak ve veri tekrarı önlemek amacıyla, kayıtları tablolara dağıtıyoruz. Böylelikle, bu tablolar arasında, yabancı anahtarlar üzerinden, bire-bir, bire-çoklu, çokludan-bire ve çokludan-çokluya türünde ilişkiler yaratıyoruz. Bu yaklaşım, veri tutarlılığı açısından gerekli olsa da veriye okumak ya da değiştirmek amacıyla eriştiğimizde başarımın düşmesine neden olmaktadır. İlişkisel veri tabanları bu durumu aşmak için çeşitli çözümler geliştirseler de yüksek TPS (Transactions Per Second) değerleri önündeki en büyük engel olarak duruyor. MongoDB'de verileri aynı ilişkisel veri tabanlarında olduğu gibi farklı torbalara dağıtabilir ve bu torbalar arasında yabancı anahtarlar üzerinden ilişkiler kurabiliriz. Ancak MongoDB'de bu çalışma şeklinde yüksek başarım alınamaz, yüksek ölçeklenebilirliğe sahip uygulamalar geliştirilemez. Bunun yerine, torbalarda ilişkileriyle beraber tüm verileri saklamak daha uygun olur. 

Şekil-4: MongoDB'de Veri tabanı, Torba ve Doküman İlişkisi

Örneğimizde, imdb veri tabanındaki movies1 torbasında, film bilgileri, json dokümanı olarak saklanmaktadır. movies1 torbasındaki bir filme ulaştığımızda, filmin adına, yılına, yönetmenlerinin bilgilerine ve tür bilgilerine ulaşıyoruz:
mongo
MongoDB shell version: 3.4
connecting to: test
> use imdb
switched to db imdb
> show collections
directors2
genres2
histogram
movies1
movies2
system.indexes
> db.movies1.find({"_id": 1})
{ "_id" : 1, "directors" : [ { "_id" : 1, "imdb" : "nm1989536", "name" : "Marc Webb" } ], "genres" : [ { "name"
: "Comedy", "_id" : 1 }, { "name" : "Drama", "_id" : 2 }, { "name" : "Romance", "_id" : 3 } ], "imdb" : "tt10226
03", "title" : "500 Days Of Summer", "year" : 2007 }
>

Buna karşılık olarak movies2 torbasında ise doğrudan yönetmen ve tür bilgisi bulunmuyor. Türlere ve yönetmenlere erişmek için ayrı torbalar var: directors2, genres2
mongo
MongoDB shell version: 3.4
connecting to: test
> use imdb
switched to db imdb
> db.movies2.find({"_id": 1})
{ "_id" : 1, "directors" : [ { "id" : 1 } ], "genres" : [ { "id" : 1 }, { "id" : 2 }, { "id" : 3 } ], "imdb" : "
tt1022603", "title" : "500 Days Of Summer", "year" : 2007 }
>

Birinci tasarımda, veri tutarlılığını sağlamak geliştiricinin üstlendiği bir göreve dönüşmüştür. Buna karşılık birinci yöntemde veriye erişim başarımı en üst noktadadır. İkinci yöntemde ise veri tutarlılığı sağlanırken ve veri tekrarı önlenirken başarım düşecektir. Üstelik MongoDB'de tek bir torba üzerindeki tek bir ekleme, silme, güncelleme işlemleri atomik olarak gerçeklenir, ancak aynı ya da farklı torbalar üzerindeki bir dizi işlem, atomik olarak gerçeklenmesi garanti edilmez. MongoDB "transactional" bir veri tabanı değildir. Bu nedenle verilerin hayati derecede kritik olduğu, veri kaybına ya da tutarsızlığına tahammülün olmadığı uygulamalarda kullanılmamalıdır.  Ancak ACID uyumluluğuna sahip "transactional" bir MongoDB sunucusu bulunuyor: TokuMX. Şimdilik sadece Linux dağıtımları mevcut. Ücretsiz açık kaynak kodlu topluluk sürümüne bu bağlantıdan erişebilirsiniz. TokuMX fraktal ağaç yaklaşımını kullanmaktadır. 
Yükleme işleminin başarılı olduğunu test etmek için sunucuya mongo istemcisi ile bağlanalım ve veri tabanlarını ve bu veri tabanlarındaki torbaların listesini alalım:
mongo
MongoDB shell version: 3.4
connecting to: test
> show dbs
admin  (empty)
imdb   0.078GB
local  0.078GB
world  0.078GB
> use imdb
switched to db imdb
> show collections
directors2
genres2
movies1
movies2
system.indexes
> use world
switched to db world
> show collections
cities2
countries1
countries2
system.indexes
>

Torbalar Üzerinde Çalışmak

Çalışmaya önce bir torbaya kayıt ekleyerek başlayalım:
mongo
MongoDB shell version: 3.4
connecting to: test
> use imdb
switched to db imdb
> var tt2179136= {};
> tt2179136.title="American sniper";
American sniper
> tt2179136.year=Number(2014);
2014
> tt2179136.imdb= "tt2179136";
tt2179136
> var nm0000142 = {};
> nm0000142.name= "Clint Eastwood";
Clint Eastwood
> nm0000142.imdb= "nm0000142";
nm0000142
> tt2179136.directors= [ nm0000142 ];
[ { "name" : "Clint Eastwood", "imdb" : "nm0000142" } ]
> var action= { "name": "Action"}
> var biography= { "name": "Biography"}
> var drama= { "name": "Drama"}
> tt2179136.genres= [ action, biography, drama ];
[
        {
                "name" : "Action"
        },
        {
                "name" : "Biography"
        },
        {
                "name" : "Drama"
        }
]
> db.movies1.insert(tt2179136);
WriteResult({ "nInserted" : 1 })
>

Eklediğimiz bu kayda ulaşmak için ise aşağıdaki çağrıyı oluşturuyoruz:
> db.movies1.findOne({ "imdb": "tt2179136"})
{
        "_id" : ObjectId("54d764fc492627f560e3fe41"),
        "title" : "American sniper",
        "year" : 2014,
        "imdb" : "tt2179136",
        "directors" : [
                {
                        "name" : "Clint Eastwood",
                        "imdb" : "nm0000142"
                }
        ],
        "genres" : [
                {
                        "name" : "Action"
                },
                {
                        "name" : "Biography"
                },
                {
                        "name" : "Drama"
                }
        ]
}

Dikkat edilirse, suncunun torbaya attığım json dokümana fazladan bir alan eklediği görülür: _id. Bu alan torbadaki her bir doküman için ister biz eklemiş olalım isterse de sunucu tarafından otomatik olarak verilmiş olsun, istisnasız vardır. _id alanı aslında ObjectId tipinde bir başka json nesnesidir. ObjectId 12 sekizlikten oluşan bir dizi alandan oluşur (Şekil-5). 4-Sekizlik boyutundaki ilk alan dokümanın eklenme tarihini saklar. Bu bilgiye getTimestamp() çağrısı ile ulaşabiliriz:
> var sniper= db.movies1.findOne({"title": /sniper/});
> sniper._id
ObjectId("54d78d5bb93815e7249f4e6b")
> sniper._id.getTimestamp()
ISODate("2015-02-08T16:22:51Z")
>

2-sekizlik boyundaki alan mongod prosesinin proses kimliğinden (PID), 3-sekizlik boyundaki alan makinanın IP değerinden ve 3-sekizlik alan ise sayaç değerinden oluşur. Bu şekilde _id değerinin tüm evrende tekil bir değer olması sağlanır. Bu kimlik değerini karakter katarı olarak okumak mümkündür:
> sniper._id.str
54d78d5bb93815e7249f4e6b
> sniper._id.toString()
ObjectId("54d78d5bb93815e7249f4e6b")

Şekil-5: ObjectID alanları

Sadece filmin adına ve yılına ulaşmak için ise ikinci bir parametre daha vermemiz gerekir:
> db.movies1.findOne({ "imdb": "tt2179136"},{"title": 1, "year":2, "_id": 0})
{ "title" : "American sniper", "year" : 2014 }

Tüm filmlerin listesine ulaşmak için ise find() çağrısını kullanıyoruz:
> var cur= db.movies1.find({})
> cur.next()
{
        "_id" : 1,
        "directors" : [
                {
                        "_id" : 1,
                        "imdb" : "nm1989536",
                        "name" : "Marc Webb"
                }
        ],
        "genres" : [
                {
                        "name" : "Comedy",
                        "_id" : 1
                },
                {
                        "name" : "Drama",
                        "_id" : 2
                },
                {
                        "name" : "Romance",
                        "_id" : 3
                }
        ],
        "imdb" : "tt1022603",
        "title" : "500 Days Of Summer",
        "year" : 2007
}
>

find() her zaman bir imleç (=cursor) döner. Bu kayıtlara ulaşmak için bir JS nesnesidir. next() çağrısını kullanarak bir sonraki dokümana ulaşabiliriz. İmleci kullanarak skip() ve limit() çağrıları yardımı ile sayfalama yapmak mümkündür: 
> var cur= db.movies1.find({},{"title": 1, "_id":0})
> cur.skip(10).limit(10)
{ "title" : "Armored" }
{ "title" : "Bornova Bornova" }
{ "title" : "Coco avant Chanel" }
{ "title" : "Nefes: Vatan sa?olsun" }
{ "title" : "Up" }
{ "title" : "Whiteout" }
{ "title" : "The Time Travelers Wife" }
{ "title" : "Whatever Works" }
{ "title" : "Anonyma - Eine Frau in Berlin" }
{ "title" : "Weather Girl" }
>

Az önce eklediğimiz filmi silelim:
> db.movies1.remove( {"imdb": "tt2179136"})
WriteResult({ "nRemoved" : 1 })
> db.movies1.find( {"imdb": "tt2179136"})

world veri tabanındaki countries1 torbasındaki tüm Asya kıtasında yer alan ülkelerin nüfuslarını bir arttıralım:
> use world
switched to db world
> db.countries1.find({"continent": "Asia"}).forEach(
... function(country){
... db.countries1.findAndModify({
...    query:{"_id" : country._id },
...    update:{
...   $inc:{population: 1}
...    }
... });
... });
>

Torbalar Üzerinde Sorgu Çalıştırmak
MongoDB'yi yine JS kullanarak sorgulayacağız. Bunun için hem imdb hem de world veri tabanları üzerinde bir dizi sorgu problemini sırayla çalışacağız.

1. Tüm kıtaların listesi:
> use world
switched to db world
> db.countries1.distinct('continent')
[
        "North America",
        "Asia",
        "Africa",
        "Europe",
        "Oceania",
        "South America"
]

2. Toplam kıta sayısı:
> use world
switched to db world
> db.countries1.distinct('continent').length
6

3. Her bir kıtadaki ülke sayısı:
> use world
switched to db world
> db.countries1.group(
...   {
...    "key": { "continent": 1}, initial: {count :0},
...    "reduce": function(c,h){ h.count = h.count +1;}
...   }
... );
[
        {
                "continent" : "North America",
                "count" : 37
        },
        {
                "continent" : "Asia",
                "count" : 51
        },
        {
                "continent" : "Africa",
                "count" : 58
        },
        {
                "continent" : "Europe",
                "count" : 46
        },
        {
                "continent" : "South America",
                "count" : 14
        },
        {
                "continent" : "Oceania",
                "count" : 28
        },
        {
                "continent" : "Antarctica",
                "count" : 5
        }
]

4. Her bir kıtadaki ülkelerin listesi:
> use world
switched to db world
> db.countries1.group(
... {
... "key": { "continent": 1},
... "initial": {
...                "countries" : []
...   },
... "reduce": function(country,doc){ doc.countries.push(country.name);}
... }
... );
[
        {
                "continent" : "North America",
                "countries" : [
                        "Aruba",
                        "Anguilla",
                        "Netherlands Antilles",
                        "Antigua and Barbuda",
                        "Bahamas",
                        "Belize",
                        "Bermuda",
                        "Barbados",
                        "Canada",
                        "Costa Rica",
                        "Cuba",
                        "Cayman Islands",
                        "Dominica",
                        "Dominican Republic",
                        "Guadeloupe",
                        "Grenada",
                        "Greenland",
                        "Guatemala",
                        "Honduras",
                        "Haiti",
                        "Jamaica",
                        "Saint Kitts and Nevis",
                        "Saint Lucia",
                        "Mexico",
                        "Montserrat",
                        "Martinique",
                        "Nicaragua",
                        "Panama",
                        "Puerto Rico",
                        "El Salvador",
                        "Saint Pierre and Miquelon",
                        "Turks and Caicos Islands",
                        "Trinidad and Tobago",
                        "United States",
                        "Saint Vincent and the Grenadines",
                        "Virgin Islands, British",
                        "Virgin Islands, U.S."
                ]
        },
        {
                "continent" : "Asia",
                "countries" : [
                        "Afghanistan",
                        "United Arab Emirates",
                        "Armenia",
                        "Azerbaijan",
                        "Bangladesh",
                        "Bahrain",
                        "Brunei",
                        "Bhutan",
                        "China",
                        "Cyprus",
                        "Georgia",
                        "Hong Kong",
                        "Indonesia",
                        "India",
                        "Iran",
                        "Iraq",
                        "Israel",
                        "Jordan",
                        "Japan",
                        "Kazakstan",
                        "Kyrgyzstan",
                        "Cambodia",
                        "South Korea",
                        "Kuwait",
                        "Laos",
                        "Lebanon",
                        "Sri Lanka",
                        "Macao",
                        "Maldives",
                        "Myanmar",
                        "Mongolia",
                        "Malaysia",
                        "Nepal",
                        "Oman",
                        "Pakistan",
                        "Philippines",
                        "North Korea",
                        "Palestine",
                        "Qatar",
                        "Saudi Arabia",
                        "Singapore",
                        "Syria",
                        "Thailand",
                        "Tajikistan",
                        "Turkmenistan",
                        "East Timor",
                        "Turkey",
                        "Taiwan",
                        "Uzbekistan",
                        "Vietnam",
                        "Yemen"
                ]
        },
        {
                "continent" : "Africa",
                "countries" : [
                        "Angola",
                        "Burundi",
                        "Benin",
                        "Burkina Faso",
                        "Botswana",
                        "Central African Republic",
                        "Côte d’Ivoire",
                        "Cameroon",
                        "Congo, The Democratic Republic of the",
                        "Congo",
                        "Comoros",
                        "Cape Verde",
                        "Djibouti",
                        "Algeria",
                        "Egypt",
                        "Eritrea",
                        "Western Sahara",
                        "Ethiopia",
                        "Gabon",
                        "Ghana",
                        "Guinea",
                        "Gambia",
                        "Guinea-Bissau",
                        "Equatorial Guinea",
                        "British Indian Ocean Territory",
                        "Kenya",
                        "Liberia",
                        "Libyan Arab Jamahiriya",
                        "Lesotho",
                        "Morocco",
                        "Madagascar",
                        "Mali",
                        "Mozambique",
                        "Mauritania",
                        "Mauritius",
                        "Malawi",
                        "Mayotte",
                        "Namibia",
                        "Niger",
                        "Nigeria",
                        "Réunion",
                        "Rwanda",
                        "Sudan",
                        "Senegal",
                        "Saint Helena",
                        "Sierra Leone",
                        "Somalia",
                        "Sao Tome and Principe",
                        "Swaziland",
                        "Seychelles",
                        "Chad",
                        "Togo",
                        "Tunisia",
                        "Tanzania",
                        "Uganda",
                        "South Africa",
                        "Zambia",
                        "Zimbabwe"
                ]
        },
        {
                "continent" : "Europe",
                "countries" : [
                        "Albania",
                        "Andorra",
                        "Austria",
                        "Belgium",
                        "Bulgaria",
                        "Bosnia and Herzegovina",
                        "Belarus",
                        "Switzerland",
                        "Czech Republic",
                        "Germany",
                        "Denmark",
                        "Spain",
                        "Estonia",
                        "Finland",
                        "France",
                        "Faroe Islands",
                        "United Kingdom",
                        "Gibraltar",
                        "Greece",
                        "Croatia",
                        "Hungary",
                        "Ireland",
                        "Iceland",
                        "Italy",
                        "Liechtenstein",
                        "Lithuania",
                        "Luxembourg",
                        "Latvia",
                        "Monaco",
                        "Moldova",
                        "Macedonia",
                        "Malta",
                        "Netherlands",
                        "Norway",
                        "Poland",
                        "Portugal",
                        "Romania",
                        "Russian Federation",
                        "Svalbard and Jan Mayen",
                        "San Marino",
                        "Slovakia",
                        "Slovenia",
                        "Sweden",
                        "Ukraine",
                        "Holy See (Vatican City State)",
                        "Yugoslavia"
                ]
        },
        {
                "continent" : "South America",
                "countries" : [
                        "Argentina",
                        "Bolivia",
                        "Brazil",
                        "Chile",
                        "Colombia",
                        "Ecuador",
                        "Falkland Islands",
                        "French Guiana",
                        "Guyana",
                        "Peru",
                        "Paraguay",
                        "Suriname",
                        "Uruguay",
                        "Venezuela"
                ]
        },
        {
                "continent" : "Oceania",
                "countries" : [
                        "American Samoa",
                        "Australia",
                        "Cocos (Keeling) Islands",
                        "Cook Islands",
                        "Christmas Island",
                        "Fiji Islands",
                        "Micronesia, Federated States of",
                        "Guam",
                        "Kiribati",
                        "Marshall Islands",
                        "Northern Mariana Islands",
                        "New Caledonia",
                        "Norfolk Island",
                        "Niue",
                        "Nauru",
                        "New Zealand",
                        "Pitcairn",
                        "Palau",
                        "Papua New Guinea",
                        "French Polynesia",
                        "Solomon Islands",
                        "Tokelau",
                        "Tonga",
                        "Tuvalu",
                        "United States Minor Outlying Islands",
                        "Vanuatu",
                        "Wallis and Futuna",
                        "Samoa"
                ]
        },
        {
                "continent" : "Antarctica",
                "countries" : [
                        "Antarctica",
                        "French Southern territories",
                        "Bouvet Island",
                        "Heard Island and McDonald Islands",
                        "South Georgia and the South Sandwich Islands"
                ]
        }
]
>

5. 70'li yılların filmlerine, yıllara göre sıralı olarak erişmek:
> use imdb
switched to db imdb
> db.movies1.find(
... {
... 'year' : {$gte : 1970, $lt: 1980 }
... },
... {
... "title": 1, "year":2 , "_id": 0
... }
... ).sort({"year": 1})
{ "title" : "Le magnifique", "year" : 1973 }
{ "title" : "Dog Day Afternoon", "year" : 1975 }
{ "title" : "Network", "year" : 1976 }
{ "title" : "The Little Girl Who Lives Down the Lane", "year" : 1976 }
{ "title" : "Der amerikanische Freund", "year" : 1977 }
{ "title" : "The Last Wave", "year" : 1977 }

6. Birden fazla yönetmeni olan filmlerin listesi:
> use imdb
switched to db imdb
> db.movies1.find( { $where: "this.directors.length > 1" } , {"title": 1, "_id":0 })
{ "title" : "Gamer" }
{ "title" : "A Serious Man" }
{ "title" : "Rembetiko" }
{ "title" : "New York, I Love You" }
{ "title" : "Daybreakers" }
{ "title" : "The Imaginarium of Doctor Parnassus" }
{ "title" : "Cloudy with a Chance of Meatballs" }
{ "title" : "The Princess and the Frog" }
{ "title" : "Karamazovi" }
{ "title" : "The Missing Person" }
{ "title" : "Sonbahar" }
{ "title" : "I Love You Phillip Morris" }
{ "title" : "The Book of Eli" }
>

7. Nufus yoğunluğu düşük olan ülkelerin listesi:
> use world
switched to db world
> db.countries1.find(
... {
...           $and : [
...                { "population": { $lt: 1000000 } } ,
...                { "surfaceArea": { $gt: 100000 } }
...           ]
... } ,
...         {
...            "name": true, "population": true, "surfaceArea": true, "_id":false
...         }
... )
{ "name" : "Western Sahara", "population" : 293000, "surfaceArea" : 266012 }
{ "name" : "Greenland", "population" : 56000, "surfaceArea" : 2166102 }
{ "name" : "Guyana", "population" : 861000, "surfaceArea" : 214981 }
{ "name" : "Iceland", "population" : 279000, "surfaceArea" : 103012 }
{ "name" : "Suriname", "population" : 417000, "surfaceArea" : 163277 }
>

8. 5 karakterli olan ülke adları:
> use world
switched to db world
> db.countries1.find(
...  { name : /^....$/},
...  { name: 1 , _id: 0}
... )
{ "name" : "Cuba" }
{ "name" : "Guam" }
{ "name" : "Iran" }
{ "name" : "Iraq" }
{ "name" : "Laos" }
{ "name" : "Mali" }
{ "name" : "Niue" }
{ "name" : "Oman" }
{ "name" : "Peru" }
{ "name" : "Chad" }
{ "name" : "Togo" }

9. 70'li yıllara ait Drama türündeki filmlere, film adına göre sıralı olarak erişmek:
> use imdb
switched to db imdb
> db.movies1.find(
... {
... $and : [
... { 'year' : {$gte : 1970, $lt: 1980 } },
... { 'genres.name': { $in: [ 'Drama' ] } }
... ]
... },
... {
... "title": 1, "year":2 , "_id": 0
... }
... ).sort({"title": 1})
{ "title" : "Der amerikanische Freund", "year" : 1977 }
{ "title" : "Dog Day Afternoon", "year" : 1975 }
{ "title" : "Network", "year" : 1976 }
{ "title" : "The Last Wave", "year" : 1977 }
{ "title" : "The Little Girl Who Lives Down the Lane", "year" : 1976 } 

10. Her bir film türünden kaç tane film olduğu bilgisine ulaşmak:
> db.movies1.aggregate(
...         { $project: { "genres": 1} },
...         { $unwind: "$genres" },
...         { $group: { _id: "$genres.name" , total: { $sum: 1 } } }
... );
{ "_id" : "Documentary", "total" : 1 }
{ "_id" : "Western", "total" : 7 }
{ "_id" : "Fantasy", "total" : 14 }
{ "_id" : "Sport", "total" : 7 }
{ "_id" : "Crime", "total" : 41 }
{ "_id" : "Family", "total" : 12 }
{ "_id" : "History", "total" : 12 }
{ "_id" : "Biography", "total" : 33 }
{ "_id" : "Adventure", "total" : 26 }
{ "_id" : "Action", "total" : 36 }
{ "_id" : "Animation", "total" : 9 }
{ "_id" : "Film-Noir", "total" : 2 }
{ "_id" : "Mystery", "total" : 33 }
{ "_id" : "Musical", "total" : 2 }
{ "_id" : "War", "total" : 18 }
{ "_id" : "Horror", "total" : 19 }
{ "_id" : "Romance", "total" : 56 }
{ "_id" : "Music", "total" : 7 }
{ "_id" : "Thriller", "total" : 70 }
{ "_id" : "Drama", "total" : 193 }
{ "_id" : "Sci-Fi", "total" : 15 }
{ "_id" : "Comedy", "total" : 65 }

Torbaları İndekslemek

Sistem tüm torbaları _id alanları üzerinden indeksler. İndekslemek erişim başarımı için önemlidir. Eğer sorgularda sıkça kullanılan alanlar var ise bu alanları da indekslemek uygun olur. Şimdi indekslenmemiş bir alan üzerinde bir sorgu çalıştırmanın maliyetini görelim:
> db.movies1.find({"year": 2010}).explain();
{
        "cursor" : "BasicCursor",
        "isMultiKey" : false,
        "n" : 18,
        "nscannedObjects" : 251,
        "nscanned" : 251,
        "nscannedObjectsAllPlans" : 251,
        "nscannedAllPlans" : 251,
        "scanAndOrder" : false,
        "indexOnly" : false,
        "nYields" : 1,
        "nChunkSkips" : 0,
        "millis" : 4,
        "server" : "omegacw:27017",
        "filterSet" : false
}

Sunucunun, bu sorguda tüm torbayı taraması gerekti. Şimdi year alanı üzerine bir indeks yaratalım:
> db.movies1.ensureIndex({ year: 1})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}

Yarattığımız indeksin etkisini, explain() çağrısı ile görmeye çalışalım:
> db.movies1.find({"year": 2010}).explain();
{
        "cursor" : "BtreeCursor year_1",
        "isMultiKey" : false,
        "n" : 18,
        "nscannedObjects" : 18,
        "nscanned" : 18,
        "nscannedObjectsAllPlans" : 18,
        "nscannedAllPlans" : 18,
        "scanAndOrder" : false,
        "indexOnly" : false,
        "nYields" : 0,
        "nChunkSkips" : 0,
        "millis" : 0,
        "indexBounds" : {
                "year" : [
                        [
                                2010,
                                2010
                        ]
                ]
        },
        "server" : "omegacw:27017",
        "filterSet" : false
}

İndekslemediğimizde toplam 251 dokümanı da taraması gerekmişti. İndeksledikten sonra ise olması gerektiği gibi sadece 18 tane dokümana erişti. Sorgu sonucunun 18 filmden oluştuğuna dikkat edin:
> db.movies1.find({"year": 2010}).length()
18

MongoDB ile ilgili daha detaylı bilgi edinmek için "Front-end Development and MongoDB" ve "Building Scalable Web Applications using Node.js, MondoDB, and Knockout.js" eğitimlerinin içeriklerini inceleyebilirsiniz.

Sunday, February 1, 2015

CDI ile Olay Temelli Programlama

Dağıtık uygulama geliştirmek, geliştirilen uygulamayı çalışır halde tutmak, izlemek ve hata ayıklamak zordur. Java EE 6 ve 7 platformu bu zorluğun üstesinden gelmek için çeşitli çözümler sunmaktadır. Java EE 5 ile birlikte programlama modelinde önemli bazı değişiklikler gerçekleşmiştir. Programlama modelinin ve mimarinin temel özellikleri aşağıdaki gibi sıralanabilir:
  • Bileşen Tabanlı Programlama
  • Sıradan Java Sınıflarının Kullanımı
  • Betimsel Programlama
   Bileşen tabanlı programlamada bileşenleri yine nesneye dayalı programlamadaki sınıflar olarak kodluyoruz. Nesneye dayalı programlamada modellemeyi sınıflar aracılığı ile gerçekleştiriyoruz. Ardından bu sınıflardan nesneler yaratıyoruz. Java'da yaratılan tüm nesneler Heap alanına yerleşir. Bu nedenle nesnelere Heap nesneleri dendiği de olur. Bu nesneler, biz onlardan istekte bulunmadığımız sürece herhangi bir iş yapmazlar. Bu anlamda pasif olduklarını söyleyebiliriz. Bileşenler de birer nesnedir. Ancak bu nesneler geliştiriciler tarafından new operatörü kullanılarak yaratılmazlar. Bileşenler bir kap (=container) modeline ihtiyaç duyarlar. Kap, bileşenin yaşam döngüsünü yönetir: gerektiğinde bir tane yaratır, gerekli metotlarını çağırır ve gerektiğinde sonlandırır. Bileşenler kaptan çeşitli hizmetler alırlar. Bu hizmetler her kurumsal uygulama geliştirirken tekrar tekrar çözülmesi gereken güvenlik, kalıcılık, kaynak paylaşımı gibi problemlerin çözümleridir. Bu kap Java EE'de uygulama sunucusudur. Uygulama sunucusu bu problemlerin çözümüne bileşenler üzerinden erişmemizi sağlar. Üstelik bu çözüme kod yazarak değil betimsel programlama ile Java'ya Standard Edition 5 ile gelen notlar (=annotations) aracılığı ile erişiyoruz. Uygulama sunucusu iki kaptan oluşur: Web kabı ve EJB kabı. Her bir kabın bir dizi bileşen modeli bulunur. Web kabı Web bileşenlerini, EJB kabı EJB bileşenlerini yönetir. Servlet, JSP ve JSF sayfları birer Web bileşenidir. Yaşam döngüleri Web kabı tarafından yönetilir. Web bileşenlerinin görevi uygulamanın kullanıcıya dönük yüzünü oluşturmakla ilgili sorumlulukları yerine getirmektir. EJB kabında ise Stateless/Stateful/Singleton Session, Message-Driven Bean (MDB) bileşenleri yer alır. Bu bileşenlerin görevi ise iş mantığını çalıştırmaktır. Kısa süreli işleri Session Bean, uzun soluklu ya da yığın işleri MDB bileşenlerine yaptırıyoruz. Session Bean bileşenleri senkron, MDB ise asenkron çalışır. Senkron çalışmada isteği bileşene gönderdikten sonra cevabı bekliyoruz. Ancak cevabı aldıktan sonra çalışmamıza devam ediyoruz. 
   Sınıflar tek bir sorumluluğa sahip ya da tek bir konuda uzman olacak şekilde tasarlanır. Böylelikle sınıflar sadece uzmanlık alanlarında diğer sınıflara hizmet verirler. Bazen bir sınıf uzmanlığı dışında bir iş yapması gerektiğinde, konunun uzmanı sınıf hangisi ise ondan hizmet alır ve isteği bu sınıfa ihale eder. Bu şekilde sınıflar arasında hizmet alma-verme ilişkisi doğar. Bu ilişkinin gevşek bağlı olacak şekilde düzenlenmesi, yazılımdaki değişimin yönetilebilmesi için çok önemlidir. Bunun için hizmet alan ve hizmet veren sınıf arasına bir arayüz yerleştirilir. Bu arayüz bir sözleşme işlevi görür. Sınıflar biri birlerinden bu sözleşmeye uygun olarak hizmet alır ve hizmet verirler. Java programlama dilinde nesneler bu şekilde biri birlerinden hizmet alırlarken, istekler senkron olarak karşılanır. Hizmet alan sınıf cevap gelene kadar bekler. Ancak çok-lifli (=multi-threaded) programlama ile asenkron bir yapı kurulabilir. Hizmet alan ve hizmet veren sınıf arasında asenkron bir yapı kurmanın bir başka yöntemi araya bir mesajlaşma sistemi yerleştirmektir. Hizmet alan nesne isteğini bir mesaj olarak mesajlaşma sistemine iletir ve yoluna devam eder. Hizmet veren nesne mesajı alır ve işler. 
   Message-Driven Bean (MDB) kullanmak için JMS (Java Messaging System) sunucusuna ihtiyaç duyulur. İstemci isteğini bir mesaj olarak kuyruğa yerleştirir ve yoluna devam eder. Hizmet veren sınıf, MDB, vakit buldukça kendisine gelen mesajları alır ve işler. Eğer işlem mili saniye mertebesinde sürelerde tamamlanabilecek sürelerde ise Session Bean, saniye, dakika mertebesinde ya da daha da uzun soluklu ise MDB olarak gerçeklenir. Genellikle uygulama sunucuları üzerinde birer JMS sistemi bulunur. İstenirse JMS sunucusu ayrı olarak da kurulabilir. JMS üzerinden hizmet alan ve veren bileşenler böylelikle fiziksel olarak farklı makinalarda olabilir. Üstelik hizmet veren bileşen, Java EE'de bu MDB olacaktır, henüz ayakta olmasa bile sorun çıkmaz. Bir süre sonra MDB çalışmaya başlar ve kuyrukta biriken mesajları alarak gereğini yerine getirmeye ve iş mantığını çalıştırmaya başlar. 
   JMS üzerinden hem tasarımsal hem de fiziksel olarak gevşek bağlı yapılar oluşturulabilir. JMS tabanlı çözümün en önemli yitimi, asenkron olarak iletilen isteğin sonucu için hazırda bir çözümün olmayışıdır. İşlemin sonucunu veritabanı üzerinden ya da başka bir mesaj kuyruğu üzerinden alınabileceği çözümler kurulabilse de bu mimariyi biraz karmaşık hale getirecektir. 
   Java EE 6 ile birlikte yeni bir bileşen modelimiz daha var: CDI (Contexts and Dependency Injection) Bean. CDI en temelde hizmet alan ve veren bileşenler arasındaki bağımlılıkları yöneten bir çatı sunuyor. Tıpkı Spring, Guice, JBoss Seam gibi. Bunun dışında ilgiye dayalı programlama, olay temelli programlama ve dekoratör tasarım kalıbı gibi çözümlere hızlı bir şekilde ulaşmamızı sağlar. CDI'nın şartnamesine bu bağlantıdan erişebilirsiniz. CDI Java EE'deki diğer tüm API'lerle ve onların bileşenleriyle tümleşik çalışacak şekilde tasarlanmıştır.
   Bu yazıda CDI çatısının olay temelli programlama çözümünü inceleyeceğiz. İncelememizi Wildfly üzerinde çalışan bir Java EE 7 uygulaması üzerinde gerçekleştireceğiz. Öncelikle bir olay sınıfı tasarlamamız gerekiyor. Olay sınıfının hiç bir özelliği bulunmayan, yalın bir Java sınıfı olması istenir. Sadece özniteliklerden oluşan ve durumu değiştirilemez bir sınıf olması tercih edilir. Bu anlamda aykırı durumlar için tasarladığımız Exception sınıfları ile benzerlik gösterir. Exception sınıflarının da sadece hatayı açıklayan öznitelikler içermesini isteriz. Exception sınıfının kesinlikle hatayı çözmeye çalışan metodlar içermemelidir. Örnek uygulamamızda 1000 liranın üzerindeki para akışlarının bir web sayfasından izlenmesi sağlanıyor. Para transferi iş mantığı katmanında bir CDI bileşeni tarafından gerçekleniyor. Önce olay sınıfına bir bakalım:
package com.example.event;

import com.example.domain.Account;

public class OverdraftEvent {
 private Account account;
 private double amount;

 public OverdraftEvent(Account account, double amount) {
  this.account = account;
  this.amount = amount;
 }

 public Account getAccount() {
  return account;
 }

 public double getAmount() {
  return amount;
 }

 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + ((account == null) ? 0 : account.hashCode());
  return result;
 }

 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  OverdraftEvent other = (OverdraftEvent) obj;
  if (account == null) {
   if (other.account != null)
    return false;
  } else if (!account.equals(other.account))
   return false;
  return true;
 }

 @Override
 public String toString() {
  return "OverdraftEvent [account=" + account + ", amount=" + amount
    + "]";
 }

}
Şimdi olayı yayınlayacak, iş mantığı katmanındaki bileşene ve arayüzüne göz atalım:
AccountService.java:
package com.example.service;

public interface AccountService {
 boolean transferMoney(String from, String to, double amount);
}
SimpleAccountService.java:
package com.example.service.impl;
import javax.enterprise.event.Event;
import javax.inject.Inject;
import javax.inject.Named;
import javax.inject.Singleton;

import com.example.dao.AccountDao;
import com.example.domain.Account;
import com.example.domain.InsufficientBalance;
import com.example.event.OverdraftEvent;
import com.example.service.AccountService;

@Named
@Singleton
public class SimpleAccountService implements AccountService {
 @Inject
 private Event<OverdraftEvent> event;
 @Inject
 private AccountDao accountDao;

 @Override
 public boolean transferMoney(String from, String to, double amount) {
  Account fromAccount = accountDao.findAccountByIban(from);
  if (fromAccount == null)
   return false;
  Account toAccount = accountDao.findAccountByIban(to);
  if (toAccount == null)
   return false;
  try {
   fromAccount.withdraw(amount);
   toAccount.deposit(amount);
   if (amount > 1_000) {
    OverdraftEvent oe = new OverdraftEvent(fromAccount, amount);
    System.err.println("Firing an event: " + oe);
    event.fire(oe);
   }
  } catch (InsufficientBalance e) {
   return false;
  }
  return true;
 }
}
   Olayı yayınlayacak bileşenin, basitçe Event tipinde bir nesneyi, öznitelik olarak @Inject etmesi ve daha sonra olay gerçekleştiğinde fire çağrısını kullanarak olayı tetiklemesi yeterli olur. Burada bileşenin, bu olayı hangi bileşenlerin dinlendiğini bilmez. Benzer şekilde bu olayın gözlemcileri de olayın hangi bileşen tarafından yayınlandığını bilmeleri gerekmez. Böylelikle bileşenler arasında JMS gibi bir ağır sıklet çözüm kullanılmaksızın gevşek bağlı bir yapı kurulur. Ama burada CDI API'sinin JMS'in yerini aldığı düşünülmemelidir. JMS'in belirgin olarak üstün olduğu noktalar aşağıdaki gibi sıralanabilir:

  • JMS ile kümeleme yapılabilir. CDI ise sadece aynı Java Sanal Makinasındaki bileşenler için yerel bir çözüm üretir.
  • JMS mesajların alındığını garanti eder. Hata oluşursa tekrar deneyebilir.
  • JMS sunucuları dağıtık atomik işlemlerde yer alabilir. 

   Gözlemci sınıf ise bu örnek uygulamada Websocket için tasarlanmış bir @ServerEndpoint bileşeni olacaktır:
package com.example.web.endpoint;

import java.io.IOException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

import javax.enterprise.event.Observes;
import javax.inject.Singleton;
import javax.websocket.EncodeException;
import javax.websocket.OnClose;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

import com.example.event.OverdraftEvent;

@ServerEndpoint(value="/monitor",encoders=OverdraftEventEncoder.class)
@Singleton
public class AccountMonitor {
 private static Set<Session> peers = Collections
   .synchronizedSet(new HashSet<Session>());

 public AccountMonitor() {
  System.err.println("AccountMonitor is created!");
 }

 @OnOpen
 public void onOpen(Session peer) {
  System.err.println("Session is created: " + peer);
  peers.add(peer);
 }

 @OnClose
 public void onClose(Session peer) {
  System.err.println("Session is closed: " + peer);
  peers.remove(peer);
 }

 public void sendEvent(@Observes OverdraftEvent event) {
  System.err.println("Event has been received!");
  System.err.println(event);
  for (Session peer : peers) {
   try {
    peer.getBasicRemote().sendObject(event);
   } catch (IOException | EncodeException e) {
    e.printStackTrace();
   }
  }
 }
}
Websocket standardı HTML 5 ile gelen ve web tarayıcısı ile sunucu arasında tam çift yönlü (=Full Dublex) haberleşme sağlayan yeni bir protokoldür. Bu protokol yardımı ile istemci istekte bulunmasa bile sunucu web tarayıcısına veri gönderebilir. Bu örnek uygulamada, olay yaratıldığında, bu olayı sunucudan tarayıcılara göndererek, oturum açarak sunucuya bağlı bulunan kullanıcıları anında bilgilendirmek istiyoruz. sendEvent(@Observes OverdraftEvent event) metodunun imzasına baktığımızda önce olay tipinde (OverdraftEvent) bir parametre aldığını görüyoruz. Bu parametreye ayrıca @Observes notunun düşüldüğünü görüyoruz. Bu şekilde sendEvent metodunun OverdraftEvent olaylarına abone olmasını sağladık. Dergi aboneliğine benzer şekilde, derginin her yeni sayısı çıktığında tüm abonelere ulaştırılır. Her bir olay fırlatıldığında bu olayın gözlemcilerine haber verilir. Uygulamanın kullanımına ve çalışmasına ilişkin örnek bir çıktı aşağıdaki şekilde verilmiştir:
Tüm uygulamanın kaynak koduna bu bağlantıdan ulaşabilirsiniz. CDI ve Java EE 7 ile ilgili daha detaylı bilgi edinmek için "Developing Enterprise Applications on Java EE 7" ve "Contexts and Dependency Injection (CDI)" eğitimlerinin içeriklerini inceleyebilirsiniz.

Tuesday, January 27, 2015

Oracle Veritabanları Arasında Veri Aktarımı

Oracle veritabanları arasında tablo, indeks ya da kayıt aktarımı için en basit yöntem Oracle SQL Developer'ın "Database Export" özelliğini kullanmaktır. Bu özelliğe, menüdeki Tools başlığı altında yer alan Database Export alt başlığından ulaşılabilir: 
Database Export menü alt başlığı
Bizi beş aşamalı bir süreç bekliyor. İlk aşamasında bağlantıyı, DDL (=Data Definition Language) özelliklerini, kayıtların nasıl şekillendirileceğini ve aktarım dosyasının konumunu ve adını seçiyoruz:
İkinci adımda hangi bileşenlerin aktarılacağını seçiyoruz. Veri tabanında yaratabileceğimiz ve tanımlayabileceğimiz ne varsa hepsini aktarabiliriz:
Bir sonraki adımda aktarım için seçtiğimiz bileşenler içinde seçim yapıyoruz:
 Tablolar, indeksler arasından seçim yapabiliriz.
Son olarak aktarmak istediğimiz bileşenin verileri arasında seçim yapıyoruz:
Bu seçimi where cümlesi ile gerçekleştirebiliriz. Sihirbazın son diyalog penceresinde özetçe yer alıyor:
Finish butonuna basarak aktarımı onaylıyoruz. Aktarım yüküne bağlı olarak işlem uzun sürebilir. Uzun sürecek bir işlem ise arka plana atarak çalışmamıza devam edebiliriz:
Eğer tablolarda örneğin görüntü saklanan LOB alanlar varsa bu durumda Export Data Format olarak loader seçmek uygun olur: