Yüksek Performanslı Bir Çevrimdışı Deneyim İçin Google Base ve Google Gears'ı Kullanma

"Google API'leri ile Daha İyi Ajax Uygulamaları Geliştirme" serisinin ilk makalesi.

Dion Almaer ve Pamela Fox, Google
Haziran 2007

Editörün Notu: Google Gears API artık kullanılamamaktadır.

Giriş

Google Base ile Google Gears'ı birleştirerek çevrimdışı kullanılabilen bir uygulamanın nasıl oluşturulacağını gösteriyoruz. Bu makaleyi okuduktan sonra Google Base API hakkında daha fazla bilgi edinecek ve kullanıcı tercihlerini ve verilerini depolamak ve bunlara erişmek için Google Gears'ı nasıl kullanacağınızı anlayacaksınız.

Uygulamayı Anlama

Bu uygulamayı anlamak için öncelikle Google Base hakkında bilgi sahibi olmanız gerekir. Google Base, ürünler, yorumlar, yemek tarifleri ve etkinlikler gibi çeşitli kategorilerdeki öğeleri içeren büyük bir veritabanıdır.

Her öğe; başlık, açıklama, verilerin orijinal kaynağına bağlantı (varsa) ve kategori türüne göre değişen ek özelliklerle açıklanır. Google Base, aynı kategorideki öğelerin ortak bir özellik kümesini paylaştığı gerçeğinden yararlanır. Örneğin, tüm tariflerde malzemeler bulunur. Google Base öğeleri, Google web arama veya Google ürün arama sonuçlarında da zaman zaman gösterilir.

Base with Gears adlı demo uygulamamız, Google Base'e benzer şekilde yapabileceğiniz yaygın aramaları (ör. "çikolatalı" yemek tarifleri veya "sahilde yürüyüş" içeren kişisel reklamlar) saklamanıza ve görüntülemenize olanak tanır. Bu özelliği, aramalara abone olmanıza ve uygulamayı tekrar ziyaret ettiğinizde veya uygulama her 15 dakikada bir güncellenmiş feed'leri aradığında güncellenmiş sonuçları görmenize olanak tanıyan bir "Google Base Okuyucu" olarak düşünebilirsiniz.

Uygulamayı genişletmek isteyen geliştiriciler, arama sonuçlarında yeni sonuçlar olduğunda kullanıcıyı görsel olarak uyarma, kullanıcının favori öğeleri (çevrimdışı ve çevrimiçi) yer işaretlerine eklemesine (yıldızla işaretlemesine) izin verme ve kullanıcının Google Base gibi kategoriye özel özellik aramaları yapmasına izin verme gibi daha fazla özellik ekleyebilir.

Google Base Data API feed'lerini kullanma

Google Base, Google Data API çerçevesine uygun olan Google Base Data API ile programatik olarak sorgulanabilir. Google Data API protokolü, web'de okuma ve yazma için basit bir protokol sağlar ve birçok Google ürünü (ör. Picasa, E-Tablolar, Blogger, Takvim, Not Defteri) tarafından kullanılır.

Google Data API biçimi, XML ve Atom Yayınlama Protokolü'ne dayalıdır. Bu nedenle, okuma/yazma etkileşimlerinin çoğu XML biçimindedir.

Google Data API'ye dayalı bir Google Base feed'i örneği:
http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera

snippets feed türü, öğelerin herkese açık feed'ini bize sağlar. -/products, feed'i ürün kategorisiyle kısıtlamamıza olanak tanır. bq= parametresi ise feed'i daha da kısıtlayarak yalnızca "dijital fotoğraf makinesi" anahtar kelimesini içeren sonuçları göstermemizi sağlar. Bu feed'i tarayıcıda görüntülerseniz eşleşen sonuçlara sahip <entry> düğümlerini içeren XML'yi görürsünüz. Her giriş; yazar, başlık, içerik ve bağlantı öğelerini içerir. Ayrıca, kategoriye özel ek özellikler (ör. ürün kategorisindeki öğeler için "fiyat") de bulunur.

Tarayıcıdaki XMLHttpRequest'in alanlar arası kısıtlaması nedeniyle, JavaScript kodumuzda www.google.com adresinden bir XML feed'ini doğrudan okumamıza izin verilmez. XML'i okumak ve uygulamamızla aynı alan adındaki bir konumda tekrar oluşturmak için sunucu tarafında bir proxy ayarlayabiliriz ancak sunucu tarafı programlamadan tamamen kaçınmak istiyoruz. Neyse ki bunun bir alternatifi var.

Diğer Google Veri API'leri gibi, Google Base Veri API'si de standart XML'ye ek olarak bir JSON çıkışı seçeneğine sahiptir. Daha önce gördüğümüz feed'in JSON biçimindeki çıkışı şu URL'de olur:
http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera&alt=json

JSON, hiyerarşik iç içe yerleştirmeye ve çeşitli veri türlerine olanak tanıyan hafif bir değişim biçimidir. Ancak daha da önemlisi, JSON çıkışı yerel JavaScript kodunun kendisidir. Bu nedenle, alanlar arası kısıtlamayı atlayarak bir komut dosyası etiketinde referans verilerek web sayfanıza yüklenebilir.

Google Veri API'leri, JSON yüklendikten sonra yürütülecek bir geri çağırma işleviyle "json-in-script" çıkışı belirtmenize de olanak tanır. Bu sayede, komut dosyası etiketlerini sayfaya dinamik olarak ekleyip her biri için farklı geri çağırma işlevleri belirleyebildiğimizden JSON çıkışıyla çalışmak daha da kolaylaşır.

Bu nedenle, bir Base API JSON feed'ini sayfaya dinamik olarak yüklemek için feed URL'siyle (altcallback değerleri eklenmiş) bir komut dosyası etiketi oluşturan ve bunu sayfaya ekleyen aşağıdaki işlevi kullanabiliriz.

function getJSON() {
  var script = document.createElement('script');

  var url = "http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera";
  script.setAttribute('src', url + "&alt=json-in-script&callback=listResults");
  script.setAttribute('type', 'text/JavaScript');
  document.documentElement.firstChild.appendChild(script);
}

Bu nedenle, geri çağırma işlevimiz listResults artık tek parametre olarak iletilen JSON'da yineleme yapabilir ve bulunan her girişle ilgili bilgileri madde işaretli bir listede gösterebilir.

  function listTasks(root) {
    var feed = root.feed;
    var html = [''];
    html.push('<ul>');
    for (var i = 0; i < feed.entry.length; ++i) {
      var entry = feed.entry[i];
      var title = entry.title.$t;
      var content = entry.content.$t;
      html.push('<li>', title, ' (', content, ')</li>');
    }
    html.push('</ul>');

    document.getElementById("agenda").innerHTML = html.join("");
  }

Google Gears'ı ekleme

Google Base ile Google Data API üzerinden iletişim kurabilen bir uygulamamız olduğundan, bu uygulamanın çevrimdışı çalışmasını istiyoruz. Google Gears bu noktada devreye girer.

Çevrimdışı çalışabilen bir uygulama yazarken çeşitli mimari seçenekler arasından seçim yapabilirsiniz. Uygulamanın çevrimiçi ve çevrimdışı olarak nasıl çalışması gerektiğiyle ilgili sorular soracaksınız (ör. Tamamen aynı şekilde mi çalışıyor? Arama gibi bazı özellikler devre dışı mı? Senkronizasyonu nasıl ele alacaksınız?)

Bizim durumumuzda, Gears'ın yüklü olmadığı tarayıcılarda kullanıcıların uygulamayı kullanmaya devam edebilmesini sağlamak ve eklentinin yüklü olduğu tarayıcılarda kullanıcılara çevrimdışı kullanım ve daha hızlı yanıt veren bir kullanıcı arayüzü sunmak istiyorduk.

Mimari yapımız şu şekildedir:

  • Arama sorgularınızı depolamaktan ve bu sorgulardan sonuç döndürmekten sorumlu bir JavaScript nesnemiz var.
  • Google Gears yüklüyse her şeyi yerel veritabanında depolayan bir Gears sürümü edinirsiniz.
  • Google Gears yüklü değilse sorguları çerezde depolayan ve sonuçlar bir çerezde depolanamayacak kadar büyük olduğundan sonuçların tamamını depolamayan bir sürüm (bu nedenle biraz daha yavaş yanıt verir) elde edersiniz.
Bu mimarinin güzel yanı, if (online) {} için mağazanın her yerinde kontrol yapmanız gerekmemesidir. Bunun yerine, uygulamada bir Gears kontrolü yapılır ve ardından doğru adaptör kullanılır.


Gears Yerel Veritabanı Kullanma

Gears'ın bileşenlerinden biri, yerleştirilmiş ve kullanıma hazır olan yerel SQLite veritabanıdır. MySQL veya Oracle gibi sunucu tarafı veritabanları için daha önce API'ler kullandıysanız basit bir veritabanı API'si olduğunu fark edeceksiniz.

Yerel veritabanı kullanma adımları oldukça basittir:

  • Google Gears nesnelerini başlatma
  • Veritabanı fabrikası nesnesi alma ve veritabanı açma
  • SQL isteklerini yürütmeye başlama

Bunları hızlıca inceleyelim.


Google Gears Nesnelerini Başlatma

Uygulamanız, /gears/samples/gears_init.js içeriğini doğrudan okumalı veya kodu kendi JavaScript dosyanıza yapıştırmalıdır. <script src="..../gears_init.js" type="text/JavaScript"></script> çalışmaya başladığında google.gears ad alanına erişebilirsiniz.


Veritabanı Fabrikası Nesnesi Alma ve Veritabanı Açma
var db = google.gears.factory.create('beta.database', '1.0');
db.open('testdb');

Bu tek çağrı, veritabanı şemasını açmanıza olanak tanıyan bir veritabanı nesnesi verir. Veritabanlarını açtığınızda, aynı kaynak politikası kuralları aracılığıyla kapsamlandırılırlar. Bu nedenle, "testdb" veritabanınız benim "testdb" veritabanımla çakışmaz.


SQL İsteklerini Yürütmeye Başlama

Artık veritabanına SQL istekleri göndermeye hazırız. "Seç" istekleri gönderdiğimizde, istediğimiz veriler için yineleyebileceğimiz bir sonuç kümesi alırız:

var rs = db.execute('select * from foo where name = ?', [ name ]);

Döndürülen sonuç kümesi üzerinde aşağıdaki yöntemlerle işlem yapabilirsiniz:

booleanisValidRow()
voidnext()
voidclose()
intfieldCount()
stringfieldName(int fieldIndex)
variantfield(int fieldIndex)
variantfieldByName(string fieldname)

Daha fazla bilgi için lütfen Database Module API dokümanlarına bakın. (Editörün Notu: Google Gears API artık kullanılamamaktadır).


Düşük Seviyeli API'yi Kapsüllemek İçin GearsDB'yi Kullanma

Sık kullanılan bazı veritabanı görevlerini kapsayıp daha kolay hale getirmek istedik. Örneğin,

  • Uygulamada hata ayıklama yaparken oluşturulan SQL'i güzel bir şekilde kaydetmek istiyorduk.
  • İstisnaları her yerde try{}catch(){} yapmak yerine tek bir yerden yönetmek istiyorduk.
  • Veri okuma veya yazma işlemlerinde sonuç kümeleri yerine JavaScript nesneleriyle çalışmak istiyorduk.

Bu sorunları genel bir şekilde ele almak için, Database nesnesini sarmalayan bir açık kaynak kitaplık olan GearsDB'yi oluşturduk. Şimdi GearsDB'nin nasıl kullanılacağını göstereceğiz.

İlk Kurulum

window.onload kodumuzda, kullandığımız veritabanı tablolarının mevcut olduğundan emin olmamız gerekir. Aşağıdaki kod çalıştırıldığında kullanıcının cihazında Gears yüklüyse kullanıcı bir GearsBaseContent nesnesi oluşturur:

content = hasGears() ? new GearsBaseContent() : new CookieBaseContent();

Ardından, veritabanını açıp tabloları oluştururuz (henüz oluşturulmamışsa):

db = new GearsDB('gears-base'); // db is defined as a global for reuse later!

if (db) {
  db.run('create table if not exists BaseQueries' +
         ' (Phrase varchar(255), Itemtype varchar(100))');
  db.run('create table if not exists BaseFeeds' + 
         ' (id varchar(255), JSON text)');
}

Bu noktada, sorguları ve feed'leri depolayacak bir tablomuz olduğundan eminiz. new GearsDB(name) kodu, belirtilen ada sahip bir veritabanının açılmasını kapsar. run yöntemi, daha düşük düzeydeki execute yöntemini sarmalar ancak hata ayıklama çıkışını konsola gönderme ve istisnaları yakalama işlemlerini de gerçekleştirir.


Arama Terimi Ekleme

Uygulamayı ilk kez çalıştırdığınızda herhangi bir aramanız olmaz. Ürünlerde Nintendo Wii araması yapmaya çalıştığınızda bu arama terimini BaseQueries tablosuna kaydederiz.

addQuery yönteminin Gears sürümü, girişi alıp insertRow aracılığıyla kaydederek bunu yapar:

var searchterm = { Phrase: phrase, Itemtype: itemtype };
db.insertRow('BaseQueries', searchterm); 

insertRow, bir JavaScript nesnesi (searchterm) alır ve bunu tabloya INSERT etmenizi sağlar. Ayrıca kısıtlamalar (ör. birden fazla "Bob" öğesinin benzersizlik engelleme eklenmesi) tanımlamanıza da olanak tanır. Ancak çoğu zaman bu kısıtlamaları veritabanında ele alırsınız.


Tüm Arama Terimlerini Alma

Geçmiş aramalar listenizi doldurmak için selectAll adlı güzel bir seçim sarmalayıcısı kullanıyoruz:

GearsBaseContent.prototype.getQueries = function() {
  return this.db.selectAll('select * from BaseQueries');
}

Bu işlem, veritabanındaki satırlarla eşleşen bir JavaScript nesneleri dizisi (ör. [ { Phrase: 'Nintendo Wii', Itemtype: 'product' }, { ... }, ...]) döndürür.

Bu durumda, tam listeyi döndürebilirsiniz. Ancak çok fazla veriniz varsa döndürülen her satır geldiğinde üzerinde işlem yapabilmek için select çağrısında geri çağırma kullanmak isteyebilirsiniz:

 db.selectAll('select * from BaseQueries where Itemtype = ?', ['product'], function(row) {
  ... do something with this row ...
});

GearsDB'deki diğer bazı faydalı seçim yöntemleri şunlardır:

selectOne(sql, args)Eşleşen ilk/tek JavaScript nesnesini döndürür.
selectRow(table, where, args, select)Normalde SQL'i yoksaymak için basit durumlarda kullanılır.
selectRows(table, where, args, callback, select)selectRow ile aynıdır ancak birden fazla sonuç için kullanılır.

Feed Yükleme

Google Base'ten sonuç feed'i aldığımızda bunu veritabanına kaydetmemiz gerekir:

content.setFeed({ id: id, JSON: json.toJSONString() });

... which calls ...

GearsBaseContent.prototype.setFeed = function(feed) {
  this.db.forceRow('BaseFeeds', feed);
}

Öncelikle JSON feed'ini alıp toJSONString yöntemini kullanarak Dize olarak döndürürüz. Ardından feed nesnesini oluşturup forceRow yöntemine iletiyoruz. forceRow, giriş yoksa giriş EKLEYECEK veya mevcut bir girişi GÜNCELLEYECEK.


Arama Sonuçlarını Görüntüleme

Uygulamamız, belirli bir aramanın sonuçlarını sayfanın sağ panelinde gösterir. Arama terimiyle ilişkili feed'i nasıl aldığımız aşağıda açıklanmıştır:

GearsBaseContent.prototype.getFeed = function(url) {
  var row = this.db.selectRow('BaseFeeds', 'id = ?', [ url ]);
  return row.JSON;
}

Bir satırın JSON'ını aldığımıza göre, nesneleri geri almak için eval() yapabiliriz:

eval("var json = " + jsonString + ";");

Artık yarışa hazırız ve JSON'daki içerikleri sayfamıza innerHTML ile eklemeye başlayabiliriz.


Çevrimdışı erişim için kaynak deposu kullanma

İçerik yerel bir veritabanından alındığı için bu uygulama çevrimdışı da çalışmalıdır, değil mi?

Hayır. Sorun, bu uygulamayı başlatmak için JavaScript, CSS, HTML ve resimler gibi web kaynaklarını yüklemeniz gerektiğidir. Şu anki durumda, kullanıcınız aşağıdaki adımları uyguladıysa uygulama çalışmaya devam edebilir: Çevrimiçi olarak başlatma, bazı aramalar yapma, tarayıcıyı kapatmama, çevrimdışı duruma geçme. Öğeler tarayıcının önbelleğinde kalacağından bu yöntem işe yarayabilir. Peki bu durum geçerli değilse? Kullanıcılarımızın yeniden başlatma işleminden sonra uygulamaya sıfırdan erişebilmesini istiyoruz.

Bunu yapmak için LocalServer bileşenini kullanır ve kaynaklarımızı yakalarız. Bir kaynağı (ör. uygulamayı çalıştırmak için gereken HTML ve JavaScript) yakaladığınızda Gears bu öğeleri kaydeder ve tarayıcının bunları döndürme isteklerini de yakalar. Yerel sunucu, trafik polisi gibi davranır ve mağazadaki kayıtlı içerikleri döndürür.

Ayrıca, hangi dosyaları yakalamak istediğinizi sisteme manuel olarak söylemenizi gerektiren ResourceStore bileşenini de kullanırız. Birçok senaryoda uygulamanızın sürümünü oluşturmak ve işlemsel bir şekilde yükseltmelere izin vermek istersiniz. Bir dizi kaynak birlikte bir sürümü tanımlar. Yeni bir kaynak grubu yayınladığınızda kullanıcılarınızın dosyaları sorunsuz bir şekilde yükseltmesini istersiniz. Modeliniz bu şekildeyse ManagedResourceStore API'yi kullanıyorsunuz demektir.

Kaynaklarımızı yakalamak için GearsBaseContent nesnesi şunları yapar:

  1. Yakalanması gereken bir dizi dosya oluşturma
  2. LocalServer oluşturma
  3. Bir ResourceStore açın veya yeni bir ResourceStore oluşturun.
  4. Sayfaları mağazaya dahil etmek için çağrı yapın
// Step 1
this.storeName = 'gears-base';
this.pageFiles = [
  location.pathname,
  'gears_base.js',
  '../scripts/gears_db.js',
  '../scripts/firebug/firebug.js',
  '../scripts/firebug/firebug.html',
  '../scripts/firebug/firebug.css',
  '../scripts/json_util.js',    'style.css',
  'capture.gif' ];

// Step 2
try {
  this.localServer = google.gears.factory.create('beta.localserver', '1.0');
} catch (e) {
  alert('Could not create local server: ' + e.message);
  return;
}

// Step 3
this.store = this.localServer.openStore(this.storeName) || this.localServer.createStore(this.storeName);

// Step 4
this.capturePageFiles();

... which calls ...

GearsBaseContent.prototype.capturePageFiles = function() {
  this.store.capture(this.pageFiles, function(url, success, captureId) {
    console.log(url + ' capture ' + (success ? 'succeeded' : 'failed'));
  });
}

Burada dikkat edilmesi gereken önemli nokta, yalnızca kendi alanınızdaki kaynakları yakalayabileceğinizdir. GearsDB JavaScript dosyasına doğrudan SVN trunk'ındaki orijinal "gears_db.js" dosyasından erişmeye çalıştığımızda bu sınırlamayla karşılaştık. Çözüm elbette basittir: Harici kaynakları indirip alanınızın altına yerleştirmeniz gerekir. LocalServer yalnızca 200 (Başarılı) veya 304 (Değiştirilmedi) sunucu kodlarını kabul ettiğinden 302 veya 301 yönlendirmelerinin çalışmayacağını unutmayın.

Bu durumun sonuçları vardır. Resimlerinizi images.yourdomain.com adresine yerleştirirseniz bunları yakalayamazsınız. www1 ve www2 birbirini göremez. Sunucu tarafı proxy'ler ayarlayabilirsiniz ancak bu durumda uygulamanızı birden fazla alana bölmenin amacı ortadan kalkar.

Çevrimdışı uygulamada hata ayıklama

Çevrimdışı bir uygulamada hata ayıklamak biraz daha karmaşıktır. Artık test edilebilecek daha fazla senaryo var:

  • Uygulama önbellekte tam olarak çalışırken internete bağlıyım
  • İnternete bağlıyım ancak uygulamaya erişmedim ve önbellekte hiçbir şey yok
  • Çevrimdışıyım ancak uygulamaya eriştim
  • Çevrimdışıyım ve uygulamaya hiç erişmedim (bu iyi bir durum değil!)

Hayatı kolaylaştırmak için aşağıdaki düzeni kullandık:

  • Tarayıcının yalnızca önbellekten bir şey almadığından emin olmamız gerektiğinde Firefox'ta (veya seçtiğiniz tarayıcıda) önbelleği devre dışı bırakırız.
  • Hata ayıklama için Firebug'ı (diğer tarayıcılarda test için Firebug Lite) kullanıyoruz. Her yerde console.log() kullanıyoruz ve konsol için de tespit yapıyoruz.
  • Aşağıdaki öğelere yardımcı JavaScript kodu ekleriz:
    • veritabanını temizlememize ve temiz bir başlangıç yapmamıza olanak tanır.
    • Yakalanan dosyaları kaldırın. Böylece, yeniden yüklediğinizde dosyaları tekrar almak için internete gider (geliştirme üzerinde yineleme yaparken kullanışlıdır).

Hata ayıklama widget'ı, yalnızca Gears yüklüyse sayfanın sol tarafında gösterilir. Kod temizliği için açıklama metinleri içerir:

GearsBaseContent.prototype.clearServer = function() {
  if (this.localServer.openStore(this.storeName)) {
    this.localServer.removeStore(this.storeName);
    this.store = null;
  }
}

GearsBaseContent.prototype.clearTables = function() {
  if (this.db) {
    this.db.run('delete from BaseQueries');
    this.db.run('delete from BaseFeeds');
  }
  displayQueries();
}

Sonuç

Google Gears ile çalışmanın aslında oldukça basit olduğunu görebilirsiniz. Veritabanı bileşenini daha da kolaylaştırmak için GearsDB'yi, örneğimizde sorunsuz çalışan manuel ResourceStore'u kullandık.

En çok zaman geçirdiğiniz alan, verilerin ne zaman internete alınacağı ve nasıl çevrimdışı olarak depolanacağıyla ilgili stratejiyi tanımlar. Veritabanı şemasını tanımlamaya zaman ayırmak önemlidir. Gelecekte şemayı değiştirmeniz gerekirse mevcut kullanıcılarınızın veritabanının bir sürümü zaten olacağından bu değişikliği yönetmeniz gerekir. Bu nedenle, herhangi bir veritabanı yükseltmesinde komut dosyası kodunu göndermeniz gerekir. Bunu en aza indirmek faydalıdır. Revizyonları yönetmenize yardımcı olabilecek küçük bir kitaplık olan GearShift'i deneyebilirsiniz.

Dosyalarımızı takip etmek için ManagedResourceStore'u da kullanabilirdik. Bu durumda aşağıdaki sonuçlar ortaya çıkardı:

  • İyi birer vatandaş olarak, gelecekte sorunsuz yükseltmeler yapabilmek için dosyalarımızı sürümleyelim.
  • ManagedResourceStore'da bir URL'yi başka bir içerikle ilişkilendirmenize olanak tanıyan bir özellik vardır. Geçerli bir mimari seçeneği, gears_base.js dosyasının Gears olmayan bir sürüm olması ve Gears'ın kendisinin, tüm çevrimdışı desteğini içeren gears_base_withgears.js dosyasını indirmesi için bu sürümü takma ad olarak kullanmaktır.
Uygulamamız için tek bir arayüz kullanmanın ve bu arayüzü iki şekilde uygulamanın daha kolay olacağını düşündük.

Gearing up applications adlı kursu eğlenceli ve kolay bulduğunuzu umuyoruz. Sorularınız varsa veya paylaşmak istediğiniz bir uygulama varsa lütfen Google Gears forumuna katılın.