Thursday, August 13, 2009

Lazy initialization

Kelas yang "ramping atau tidak bergantung" dan "performa aplikasi" itu gak selalu berhubungan. belum tentu dengan ramping dan tidak bergantungnya kelas terus aplikasi pasti berjalan lebih cepat. Disini gue melihat adanya kekurang pahaman tentang prinsip loose coupled dan high cohesion. dimana prinsip kolonial VOC jaman dulu divide et impera (devide and conquer) ditelan mentah-mentah. pokoknya belah apa yang bisa dibelah, pisah apa yang bisa dipisah. akhirnya setelah semuanya dipisah, bingung make-nya gimana.

Thema ini sempat diangkat di milis id-php ketika satu teman mengalami dilema dalam memilih strategi pengembangan aplikasinya. Thread lengkapnya bisa dilihat langsung di milis.

Permasalahannya sebagai berikut:
Php-ers,

Saya lagi di rundung dilema. Dimana pada awal nya desain salah satu class Transaksi saya telah dirancang utk menjadi ramping dan tidak bergantung. Tetapi sejalan nya waktu utk mendapatkan 1 informasi, saya harus mendeklarasi class2 utk meload bbrp kepingan informasi sehingga membentuk informasi transaksi yg utuh. Maka saya menggabungkan bbrp class tsb menjadi 1.

Dan masalah mulai muncul di permukaan ketika class gabungan ini mulai byk dipakai dan membawa byk informasi, aplikasi menjadi lambat. Ini adalah link utk bisa di unduh file starUML utk melihat secara grafis dlm format UML. Utk yg satu hal 2 hal tidak dpt melihat UML. Saya mencoba utk menggambarkan nya dlm kata2.

class Transaksi pembelian menampung semua informasi pembelian, item2 yg dibeli, kemana item2 akan ditransfer, DP yg telah di bayarkan, mutasi item2 yg telah diterima. Dan setiap class memiliki detail tersendiri. jadi setiap class bersifat one-to-many. Masalah muncul ketika saya harus menampilkan daftar pembelian berikut dengan data penerimaan barang. Yang jumlah nya kalau di hitung scr matematis,
Jika Pembelian memiliki 50 item, maka akan ada data mutasi 50, data transfer 50 dan transaksi diterima yg kmungkinan bs lebih dari 5-10 kita pakai 5 transaksi yg memuat 50 data. Maka total 50 + 50 + 50 + (5 * 50) = 400 data.

Dan dalam 1 bulan bisa lbh dari 30 transaksi penjualan. Maka rata2 perbulan ada 30 * ~400 = 12000 data yg saya harus sajikan. Mungkin paging solusi yg terbaik, tetapi yg menjadi pertanyaan saya adalah bagaimana strategi atau cara terbaik agar class tetap ramping tetapi mudah utk mendapatkan data yg tersebar dlm kepingan2 kecil. Terima kasih.


Adesanto Asman

Kelas yang "ramping atau tidak bergantung" dan "performa aplikasi" itu gak selalu berhubungan. belum tentu dengan ramping dan tidak
bergantungnya kelas terus aplikasi pasti berjalan lebih cepat. Disini gue melihat adanya kekurang pahaman tentang prinsip loose coupled dan high cohesion. dimana prinsip kolonial VOC jaman dulu divide et impera (devide and conquer) ditelan mentah-mentah. pokoknya belah apa yang bisa dibelah, pisah apa yang bisa dipisah. akhirnya setelah semuanya dipisah, bingung make-nya gimana. gak perlu kecewa ^^. dalam proses belajar hal seperti ini wajar ^^. gue juga harus melewati masa-masa ini kok ^^. dan tetap harus belajar karena masih jauh dari sempurna :). gue pengen banget bahas thema ini tapi ga disini kali ya :D tar kepanjangan dan aga2 OOT.

so sekarang gue mo coba jawab inti permasalahannya. yang gue ngerti lo punya masalah terutama dalam bikin report karena lo butuh semua object dari transaction object, purchased object, details object, payment object, mutasi object dan lain sebagainya. sementara kadang dalam report itu loe paling cuma butuh 1 maksimal 3 informasi dari masing2 objekt (misalnya mungkin dari payment cuma nominalnya doang, dan dari transaction cuma transaction numbernya doang). tapi lo malah load semua informasi dari semua object. dan harus loe oper ini semua ke front end view class.

kalo emang lo punya akses ke database layer (nulis sql statement dan sejenisnya) hal ini bisa lo pecahkan dengan mendefinisikan satu select join statement. dimana di select kolom-kolom yang diperlukan saja untuk reporting. select statement ini bahkan juga bisa di simpan dalam bentuk "view" apabila databasenya mendukung. setelah itu bisa dibikin satu class baru yang member variabelnya berisi kolom2 dalam select statement ini. dengan ini lo hanya punya satu object yang hanya merequest ke satu select statement ^^.

contoh:

kira kira data access object classnya begini
class TransactionReportDAO implements InterfaceDAO{
  
  public function getStatement(){
    return "SELECT transaction.id as transaction_number,
                   purchased.id as purchased_number, purchased.amount as
                   purchased_amount, item.name as item_name *dan seterusnya*
            FROM transaction LEFT JOIN purchased ON
                 transaction.id = purchased.transaction_id
            LEFT JOIN (dan seterusnya)
            WHERE (where klausa nya);"
  }
  
  /**
   * return TransactionReport
   */
  public function getResult(){
    //populated the result to object TransactionReport
  }
}


terus model dari Entity class nya ya kira-kira kayak gini
class TransactionReport{
  
  private $transactionId;
  
  private $purchasedId;
  
  private $purchasedNumber;
  
  private $amount;
  
  private $itemName;
  
  //member variabel yang lain
  
  public function getTransactionId(){
    return $this->transactionId;
  }
  
  public function setTransactionId ($transactionId){
    $this->transactionId = $transactionId;
  }
  
  //getter dan setter yang lainnya.
  
}

dan service class nya kira2 begini:
class TransactionService{
  
  /**
   * return TransactionReport[]
   */
  public static function getAllTransactionReport(){
    
    return DBService::getInstance()->execute(new TransactionReportDAO())
                     ->getResult();
    
  }
  
  //service service yang lainnya
  
}

Dimana pada akhirnya di view loe bisa kira2 begini:
<table>
  <?php
  /* @var TransactionReport[] $dataList */
  $dataList = TransactionService::getAllTransactionReport();
/* @var TransactionReport $data */
foreach($dataList as $data) :
?>
<tr>
  <td><?php echo $data->getTransactionId(); ?></td>
  <td><?php echo $data->getPurchasedId(); ?></td>
  <td><?php echo $data->getAmount(); ?></td>
  .... dan seterusnya ...
</tr>

<?php endforeach; ?>
</table>

seperti lo lihat, loe cuma punya satu sql request, yang bisa lo modifikasi sedikit buat pagination ato sejenisnya. tapi sql requestnya tetap satu. trade off-nya tentunya tiap kali lo mo bikin report yang isinya lain loe musti bikin sql statement dan entity object yang baru. tapi dari sisi performa ini masih lebih baik.

Sekarang kalo misalnya lo ga punya akses ke database layer. dimana lo sebagai view developer cuma bisa pakai model yang sudah di definisikan. mau tidak mau memang kita waktu meng initialize objectnya kita dapat semua informasi yang ada di kolomnya meskipun kita tidak pakai. akan tetapi ada design pattern yang bisa ngebantu kita untuk meload data hanya waktu dibutuhkan saja. apabila pattern ini di implementasikan dalam meng handle model. kita masih bisa lah mempertahankan performa sedikit ^^. pattern ini umum dipakai di framework-framework ORM. namanya Lazy initialization.

gue kasih contoh penggunaannya ya.

anggap entity class lo.. seperti yang lo gambar di uml, ya mirip2 lah
class Transaction{
  
  private $transactionId;
  
  //member transaction yang lainnya
  
  /**
   * @var Purchased[]
   */
  private $purchased
    
    //getter and setter
}

class Purchased{
  private $purchasedId;
  
  //member purchased yang lainnya
  
  /**
   * @var Items[]
   */
  private Items;
  
  /**
   * @var Mutasi[]
   */
  private mutasi;
  
  /**
   * @var Payments[]
   */
  private payments;
  
  //getter and setter
}

class Items{
  
  private itemId;
  
  //member item yang lainnya
  
  //getter and setter
  
}

so class mutasi dan payments ga gue gambarin lah.. kira2 begitu.

waktu lo pake di view misalnya ya bakal kira2 kayak gini
<table>
  <?php
    /* @var Transaction[] $dataList */
    $transactionList = TransactionService::getAllTransaction();
    /* @var Transaction $transaction */
    foreach($transactionList as $transaction ) :
    
      /* @var Purchased[] $purchasedList*/
      $purchasedList= $transaction->getPurchased();
      /* @var Purchased $purchased*/
      foreach($purchasedList as $purchased) :
      
        /* @var Payments[] $paymentList*/
        $paymentList= $purchased->getPayments();
        /* @var Payment $payment*/
        foreach($paymentListas $payment) :
  ?>
  <tr>
    <td><?php echo $transaction->getTransactionId(); ?></td>
    <td><?php echo $purchased->getPurchasedId(); ?></td>
    <td><?php echo $payment->getAmount(); ?></td>
    .... dan seterusnya ...
  </tr>
  
  <?php 
        endforeach; 
      endforeach; 
    endforeach; 
  ?>
</table>

jangan kaget liat foreach nya. memang begini jadinya, kompleksitas komputasi dari satu kali foreach isi 100 data sama dengan 3 foreach isi 2x5x10 data.

dengan lazy initialization, waktu lo request $transactionList maka member variabel $purchased sebenernya masih kosong(null). select statement yang terjadi di database layer hanya untuk mengisi object Transaction saja. select statement untuk purchased, payment, mutasi dan items belum terjadi. jadi di dalam variabel $transactionList isinya cuma sedikit, yaitu informasi tentang transaction saja. informasi tentang purchased baru diminta ketika loe panggil $transaction->getPurchased();

dalam contoh gue, loe cuma request transaction, purchased dan payments saja. mutasi dan items tidak ikut di load karena gak lo request. dengan ini lo hanya berkesan bawa-bawa semua data, padahal datanya sendiri di dalam object belum ada, dan hanya diisi ketika dibutuhkan.

tentu ini pun ada tradeoff nya, seperti yang kita tahu, request database itu mahal sekali, gak peduli data yang diminta banyak atau sedikit. tapi semuanya tergantung pertimbangan design dan situasi mana yang terbaik. tehnik ini biasanya didukung sama tehnik lainnya seperti mekanisme caching misalnya, untuk mengurangi request langsung ke database.

semoga gue membantu.

No comments: