Wednesday, January 14, 2009

ORM Patterns

Kali ini gue mau membahas 2 Design Pattern untuk Object Relational Mapping. Beberapa hari lalu ada di milis ada yang tanya mengenai bedanya ORM dan Active Record. Dan gue coba untuk membahasnya disana. Supaya gak hilang gue post juga di blog gue beserta pertanyaan awalnya. Diskusi lengkapnya bisa disimak langsung di milis id-php.

Topik dibuka dengan pertanyaan sebagai berikut:
Teman2.. saya tertarik sekali dengan konsep2 baru mendesain
aplikasi,khususnya aplikasi web.
Saya lagi belajar dan baca2 artikel bout ORM dan ActiveRecord. Di buku
yg saya baca, ORM itu Object Relational Mapping. Jadi proses mapping
semua struktur pada table di database menjadi sebuah class pd OOP.


Di buku dijelaskan proses mappingnya. Yaa sampe disitu saya ngerti.
Yang bikin saya nggak ngerti,

1. Mengapa kita harus susah2 mapping? kan udah ada class2 ActiveRecord
dan class2 ORM seperti Propel utk PHP ( hmm gak pernah pake Propel )
ato pake framework2 yg udah fada itur MVC dan ActiveRecordnya. Bahkan
dengan menggunakan framework2 itu kita udah bisa kan bikin aplikasi
skala enterprise tanpa perlu design mapping secara manual.

2. Apakah class ORM dan ActiveRecord yg udah ada menyalahi aturan dan
konsep dari ORM itu sendiri?
Maksudnya, contoh kita pake cakephp. define model Student. naahh di
model student cuman ada definisi variabel2 association aja. gak ada yg
namanya definisi ( mapping ) semua entitas2 yg ada di table di class /
model Student itu. Karena semua itu dihandle oleh property $this->data
dan method save() , find() .

3. saya tertarik untuk bikin class ORM yg sangat2 simple. Yaaahh
paling nggak udah bisa save dan find one to one, dan one to many aja.
Tapi,, kebanyakan saya liat di source code-source code class2 ORM (
seperti fitur active record di cakephp ) kok sedikitpun nggak ngikut
konsep ORM yg dijelaskan di buku?

Hmmm kyknya itu aja dehh..


Terimakasih :)

polutan
Karena sudah janji mo nulis, maka gue mo nepatin janji gue.

tentang ORM sudah pernah dibahas di milist PHP sebelah. threadnya bisa dilihat disini.

threadnya juga sempat mengalami perdebatan menarik tentang masa depan ORM. Soal masa depan ORM gak gue ungkit disini ^^. Tapi gue akan coba menuturkan kekurangan dan kelebihannya dari pandangan gue. ^^

so kembali ke pertanyaannya mas polutan.
Kalo gue baca pertanyaannya dan berusaha memahami maksudnya. maka gue berkesimpulan bahwa lo mencampur aduk kan antara Konsep dasar ORM dan implementasi ORM di dalam framework (misalnya cakephp).
Gue rasa kalo lo bisa mengerti dengan benar konsep dasar ORM itu sendiri, maka lo tidak akan bertanya seperti ini. mohon maaf dulu nih kalo ternyata kesimpulan gue salah.

Supaya pemahaman lo ama gue sama maka dari itu coba kita sinkonisasi saja pemahaman ORM nya. Secara garis besar gue ambil dari wiki:

"is a programming technique for converting data between incompatible type systems in relational databases and object-oriented programming languages"

jadi tujuannya merubah informasi yang disimpan berorientasi relational (one to one, one to many, many to many), menjadi object supaya dapat dibaca dan berkolaborasi dengan aplikasi yang ditulis berorientasi obyek (inheritance, abstract, interface, composition, aggregation dsb.).

So tujuannya seperti itu. bagaimana menuju ke situ. ada banyak cara. Kalau kita bicara lo mo pergi ke satu tujuan, maka banyak cara untuk menuju ke sana (jalan kaki, naik mobil, naik angkot, kereta dsb). maka cara menuju ke ORM itu adalah Design Pattern nya. 2 Design Pattern yang terkenal dan cukup sering dipakai untuk ORM yaitu:
  • Active Record Pattern
  • Data Mapper Pattern
So sekarang mestinya kita bisa sepaham bahwa ORM itu adalah konsep/idenya dan Design Pattern adalah implementasinya. Buku-buku tentang ORM umumnya menjelaskan konsep ORM dan memberikan contoh implementasinya tanpa mengacu pada bahasa pemrogramman tertentu. apabila contohnya ditulis dalam bahasa pemrogramman tertentu (misalnya java). itu hanyalah sekedar alat bantu untuk mengerti dan memahami konsep tersebut. Pada dasarnya selama bahasa pemrogramman yang dipilih berorientasi objekt. maka sudah semestinya contoh2 tersebut juga dapat diimplementasikan menggunakan syntax bahasa tersebut. Mulai dari sini gue mau mencoba menjelaskan dulu pemahaman implementasi ORM tanpa dipengaruhi bahasa pemrogramman tertentu (misal php). syntax contohnya tentu gue bikin menggunakan syntax php, dengan harapan supaya bisa lebih dimengerti.

Kita mo mreubah informasi yang disimpan secara relational menjadi object supaya bisa berkolaborasi dengan aplikasi kita yang ditulis dengan paradigma berorientasi object. apa itu object? object itu instance dari sebuah kelas. jadi jelas sekali disini bahwa tidak perduli Design Pattern mana yang kita mo pake. semuanya butuh yang namanya kelas. untuk menciptakan object.

contoh sebuah kelas.
class Person{
  
  private $name;
  private $birthDate;
  
  public function setName($name){
    $this->name = $name;
  }
  
  public function getName(){
    return $this->name;
  }
  
  public function setBirthDate($birthDate){
    $this->birthDate = $birthDate;
  }
  
  public function getBirthDate(){
    return $this->birthDate;
  }
  
}
sebuah kelas yang indah bukan? lalu? apa hubungannya sama ORM? kalo lo kolega kerja gue dan gue yang bikin ini kelas, gue kasih elo, terus gue bilang. ini adalah ORM class dari table "Person" di database, loe bakal telan omongan gue mentah-mentah? :) that will be not so clever ^^. Sebuah kelas adalah sebuah kelas. Dia gak akan secara ajaib bisa mewakili sebuah entitas tertentu, apalagi entitas tersebut ada di system yang berbeda (database).

Maka dari itu kita butuh yang namanya mapping. Kita me-Map table dan attribute di database ke kelas yang sudah kita buat. Istilahnya kita memberikan pernyataan bahwa Kelas ini beserta member variabelnya mewakili table x beserta attributenya dalam database. Tehniknya tentu berbeda-beda, dari bahasa yang satu ke bahasa yang lain, dari framework yang satu ke framework yang lain.

Untuk contoh kelas yang diatas. maka gue kasih contoh kira2 aplikasi gue nge-map nya seperti ini

class Person extends MyORM{
  
  private $name;
  private $birthDate;
  
  public function setName($name){
    $this->name = $name;
  }
  
  public function getName(){
    return $this->name;
  }
  
  public function setBirthDate($birthDate){
    $this->birthDate = $birthDate;
  }
  
  public function getBirthDate(){
    return $this->birthDate;
  }
  
  protected function map(){
    
    $map = array(
      'table' => 'person',
      'name' => 'person_name',
      'birthDate' => 'person_birth_date'
    );
    
    return $map;
  }
  
}
sekedar contoh. ceritanya gue nge-map kelas ini ke table person di database, dan variabel member nya masing gue map ke tabel attributenya. ini sekedar contoh saja. tehnik nge-map seperti gue bilang barusan, bisa berbeda-beda.

So ini dasarnya Mapping di ORM tanpa pengaruh bahasa pemrograman tertentu. sekarang gue mau masuk ke php.

gue melihat kelas ORM gue yang panjang dan gue juga melihat dimana nama kelas dan nama tabel nya mirip, nama member variabel dan nama tabel attribute nya juga mirip2. Kalau saja gue bisa membujuk sang
database designer dan sang application programmer untuk mengikuti aturan-aturan tertentu, gue bisa membuat ORM class gue sedemikian ringkasnya, karena segala sesuatunya sudah berjalan berdasarkan aturan tertentu.

you got me bro. i'm trying to make a convention here. untuk itu gue harus melihat apakah bahasa pemrogramman gue mendukung ide gue tersebut. maka gue coba2 buka manual php.

php5 punya fungsi menarik yang dikenal dengan istilah "magic method". lebih lengkapnya silahkan baca disini.

pada prinsipnya, gue bisa bereaksi dan melakukan sesuatu apabila orang yang menggunakan object gue memanggil class member yang tidak gue definisiin di kelas gue.

sebagai contoh
$person = new Person();
$person->someVar = 'someValue';
$person->someMethod('parameter1Val', 'parameter2Val');
variabel someVar dan methode someMethod beserta parameternya, kagak gue definisiin di kelas gue diatas. akan tetapi dengan magic method gue bisa bereaksi dan melakukan sesuatu apabila ada orang yang mencoba untuk memanggil class member yang tidak gue definisikan.

karena sifat dasar php sebagai dynamic language dimana loe bisa main ngedefinisiin variabel tanpa deklarasi lebih dahulu. maka pada statement
$person->someVar = 'someValue';
lo gak akan mengalami error, hanya saja apabila satu saat nanti loe mo pake lagi, misalnya saja
echo $person->someVar
maka loe tidak mendapatkan 'someValue' sebagai output, tapi string kosong. kalo loe
var_dump($person->someVar);
hasilnya pasti NULL.

akan tetapi pada statement
$person->someMethod('parameter1Val', 'parameter2Val');
lo bakal dapet php error yang bilang "method is not defined".

Dengan menggunakan php magic method gue bisa bereaksi terhadap hal2 seperti ini. Daripada melemparkan error atau nilai null, maka gue bisa menjalankan programm logik yang konkrit atau menyimpan nilai tersebut. contohnya bisa dilihat di php manual.

satu hal lagi yang menarik. php memiliki fungsi standard bernama get_class() untuk mengetahui nama kelas sebuah object.

berbekal informasi ini. gue mau mencoba menjalankan ide gila gue. idenya seperti ini.

  • nama dari sebuah kelas mewakili nama dari sebuah tabel dalam
    database. nama kelas tersebut harus dimulai dengan huruf besar, akan
    tetapi nama tabelnya harus huruf kecil.
  • Apabila orang meng-instantiate class tersebut, maka harus terjadi
    validasi apakah terdapat sebuah tabel dengan nama kelas tersebut dalam
    database. jika tidak maka lempar TableNotExistException.
  • Nama dari member variabel mewakili nama dari table attribute. nama
    variabel harus dimulai dengan huruf kecil, dan tabel attribute juga
    harus ditulis dengan huruf kecil
  • Apabila orang mencoba memanggil atau menset nilai sebuah member
    variabel, maka harus terjadi validasi apakah terdapat sebuah tabel
    attribute dengan nama tersebut dalam database. jika tidak maka lempar
    TableAttributeNotExistException.
  • Apabila nama kelas atau member variabel lebih dari 1 suku kata, maka
    ditulis CamelCase, sedangkan di database ditulis menggunakan
    strip_bawah sebagai pemisah.
  • fungsi standard yang akan dikenal untuk sementara adalah save,
    update, dan delete.
  • fungsi dinamis yang akan dikenal adalah findBy yang diikuti dengan
    column name ditulis menggunakan CamelCase misal: findByBirthDate

Dengan ini berarti kalau gue mo nge definisiin ORM class dari person, gue tinggal tulis

class Person extends MyORM{

}
pendek sekali bukan? mengingatkan anda pada sebuah php framework. errrrr.. apa ya? ^^ pada contoh ini setiap ORM class harus meng-extend class MyORM untuk menunjukkan bahwa kelas ini adalah ORM class yang memiliki ketentuan2 seperti diatas. implementasinya sendiri terjadi di kelas MyORM. kalau mau kita bisa bikin MyORM abstract. supaya tidak bisa di instantiate oleh pengguna ORM kita.

implementasi MyORM kira2 bakal kayak gini
abstract class MyORM{
  
  /* array untuk menyimpan table attribute kita */
  private $tableAttribute = array();
  
  /* variabel untuk menyimpan table name kita */
  private $tableName = null;
  
  public function __construct(){
    
    /* ambil nama kelas menggunakan get_class($this) */
    /* jadikan huruf awal menjadi huruf kecil dan rubah
CamelCase dengan strip_bawah */
    /* periksa apakah dalam database ada kelas dengan nama tersebut */
    /* jika tidak throw TableNotExistException */
    /* jika ya maka simpan nama tabel dalam variabel $tableName */
  }
  
  public function __set($name, $value){
    /* jadikan huruf awal dari $name menjadi huruf kecil dan
rubah CamelCase dengan strip_bawah */
    /* periksa apakah dalam database ada kelas dengan nama tersebut */
    /* jika tidak throw TableAttributeNotExistException */
    /* jika ya maka simpan dalam $tableAttribute[$name] = $value */
  }
  
  public function __get($name){
    /* jadikan huruf awal dari $name menjadi huruf kecil dan
rubah CamelCase dengan strip_bawah */
    /* periksa apakah variabel tersebut sudah ada dalam
$tableAttribute */
    /* Jika ya maka return $tableAttribute[$name] */
    /* Jika tidak maka throw TableAttributeNotFoundException */
  }
  
  public function __call($name, $args){
    /* periksa apakah $name diawali dengan findBy */
    /* jika tidak throw MethodNotExistException */
    /* jika ya maka ambil text setelah findBy lalu oper ke
local variable $colName */
    /* jadikan huruf awal dari $colName menjadi huruf kecil dan
rubah CamelCase dengan strip_bawah */
    /* periksa apakah dalam database ada kelas dengan nama tersebut */
    /* jika tidak throw TableAttributeNotExistException */
    /* jika ya maka lakukan SELECT dengan where clause $colName
= $args[0] (argumen pertama) */
    /* ambil resultnya dan map setiap baris ke dalam object dan
kumpulkan object tersebut dalam satu array */
    /* return array result */
  }
  
  public function save(){
    /* save implementation */
  }
  
  public function delete(){
    /* delete implementation */
  }
  
  public function update(){
    /* update implementation */
  }
  
}
dengan begini sewaktu gue tulis
$person = new Person();
$person->personName = 'Some Name';
dia akan "magically" mapped variabel personName ke attribute person_name pada table person menggunakan "convention" yang sudah didefinisikan.

So sekarang. apakah dengan begini gue melanggar prinsip dasar ORM?. seperti yang loe lihat gue gak perlu mapping lagi. cuma bikin kelas kosong. Well. menurut gue. gue gak melanggar prinsip dasar ORM. karena mapping itu ada, hanya saja gue trick menggunakan convention over configuration. Ini gue lakukan karena "kebetulan" php memberikan gue kesempatan untuk melakukan hal ini. Ruby mungkin punya trick lain, begitu juga Java, .Net dan yang lainnya. Apabila implementasi-nya tidak sesuai dengan dibuku. sebenarnya harus dianalisa lebih jauh. apakah ini benar2 melenceng jauh dari konsep, atau hanya sekedar memanfaatkan fitur2 dari bahasa pemrogramman tersebut untuk membuat bekerja jadi lebih mudah saja, tanpa melanggar konsep dasarnya.

Sekedar info, menurut gue, result hasil dari ORM request di cakePHP kurang sesuai dengan Konsep ORM, karena hasilnya disimpan dalam array dimana semestinya disimpan dalam objekt. lebih lanjut bisa lihat disini.

Jaman sekarang emang ngetrend convention over configuration. gue sih gak gitu nge fans sama yang satu ini. emang sih bisa jadi lebih cepat. tapi gue ngerasa setiap aplikasi itu unik. dan keunikan itu gak bisa dicapai menggunakan standard. Gue perna dapat proyek bikin web aplikasi kecil buat satu bank asal jerman di luxembourg. mereka mau pake grails (groovy on rails) konsepnya sama convention over configuration. waktu kerja brutto 2 bulan. waktu kerja murni kalo gue itung2 sebenernya cuma 2 minggu. sisa waktunya adalah menyesuaikan aplikasi tersebut sesuai permintaan client. html css javascriptnya sih bukan jadi masalah besar. justru malah convention nya yang memberatkan dan bikin jadi tidak fleksibel.

gue pribadi masih jauh lebih suka kalau gue punya class yang jelas seperti contoh awal. bukan kelas kosong begitu. keuntungannya lebih banyak dan kalaupun emang capek ngetik. capeknya kebayar waktu maintanance phase. bayangin coba kalo ORM class kosong begitu. berarti loe kalo mo pake classnya kudu buka database client atau database manager dong? buat ngeliat ini kelas membernya apa aja. coba kalo loe tulis semua member variabel dan methodnya. terus loe kerja pake IDE. enak kan ada autocomplete nya. loe tinggal pilih dari autocomplete nya. gak usah pusing2 buka2 database admin. dan tentunya juga gak usah takut salah tulis ^^. Keunggulan bahasa dinamis biasanya memang bisa mengerjakan tugas dengan cepat. tapi kelebihannya itu membuat kekurangannya sendiri. banyak hal terlalu magic. methode tidak didefinisikan, tapi kalo dipanggil bisa return sesuatu. variabel tidak ada datatypenya, sewaktu2 bisa number, sewaktu2 bisa string, sewaktu2 bisa boolean, kayak banci taman lawang aja. (ngomong2 di taman lawang masih banyak banci gak ya?) hal seperti ini gak bikin aplikasi makin robust, tapi buggy.

ORM yang menggunakan convention seperti RoR atau Grails biasanya tetap punya kemampuan mapping. tapi hanya dipakai apabila nama kelas dan tabel nya tidak sama. jadi untuk costumisasi atau configurasi ^^. gue gak tahu CakePHP punya gak hal seperti itu. bayangin coba kalo $person->name sudah dipake dimana2 terus karena satu dan lain hal kita harus ganti nama kolomnya jadi "nama" gue gak akan mau disuruh cari "name" di seluruh aplikasi gue untuk diganti jadi "nama". mending gue "map" member variabel "name" untuk menunjuk ke kolom "nama". dengan ini gue cuma ganti di satu tempat.^^

Salah satu kelebihan ORM yang juga suka jadi bahan pertimbangan adalah portabilitas. ya. aplikasi lo bakal jadi tidak bergantung pada database tertentu. built once runs on any database. ya ya. marketing gag. Memangnya seberapa sering sih kita musti bikin aplikasi yang kedepannya bakal sering ganti database? gue pribadi sih belum pernah. ada baiknya loe coba perhatiin bagaimana ORM tersebut meng-handle request2 lo. terutama request berat yang berhubungan sama beberapa table. atau bagaimana dia menghandle sebuah tree structure dalam database.

Setiap database punya dialekt dan fungsi2 khusus. terutama database proprietary seperti oracle, db2 dan sebangsanya. ini dibuat bukan cuma sekedar nunjukin bahwa ini produkt proprieter. tapi lebih untuk memberikan kemudahan dan performa dalam melakukan request komplex yang tidak didefinisikan di standard SQL. mereka juga kadang memiliki algoritma sendiri yang membuat SQL Request jadi lebih cepat. hal ini tidak mudah diimplementasikan di sebuah ORM yang mau bisa jalan di semua jenis database system. kadang request yang sebenarnya bisa dilakukan hanya dengan sekali select di sebuah database (katakanlah misalnya oracle) sama ORM nya bisa jadi dilakukan dalam 5 kali select. bagaimana dia menghandle sequenze? bagaimana dia menghandle transaction? dsb dsb dsb. ini jelas merupakan hal yang menjadi bahan pertimbangan penting apakah aplikasi kita perlu pakai ORM atau langsung pakai fungsi dan dialekt database tersebut.

Semoga tulisan gue menjawab pertanyaan lo dan memberikan pencerahan daripada bikin bingung.

No comments: