Living life and Make it Better

life, learn, contribute

Endy Muhardin

Software Developer berdomisili di Jabodetabek, berkutat di lingkungan open source, terutama Java dan Linux.

Konfigurasi Shorewall di Ubuntu Gutsy

Bila kita ingin mempublish komputer di internet, hal pertama yang kita pikirkan adalah firewall. Bagaimana membatasi akses hanya ke port-port yang kita ijinkan.

Di Linux, firewall diimplementasikan dengan menggunakan aplikasi iptables. Aplikasi ini sangat powerful dan canggih. Menurut Peter Parker, with great power comes great responsibility. Akan tetapi, menurut saya, with great power comes great complexity. Untuk bisa mengoperasikan iptables dengan benar, setidaknya kita harus memahami konsep chain dan table. Setelah itu, baru kita bisa belajar tentang jenis-jenis protokol, state, port, dan hal-hal TCP/IP lainnya.

Menjelaskan konsep ini saja butuh waktu setidaknya setengah hari pada rata-rata orang yang sudah memahami konsep dasar jaringan komputer. Daripada saya bolak-balik menjelaskan konsep chain dan table, baiklah kita cari saja aplikasi front end yang sederhana.

Untungnya di Ubuntu tidak kekurangan aplikasi front end untuk iptables. Untuk komputer personal, kita bisa gunakan Firestarter. Aplikasi ini sangat mudah digunakan. Tinggal jalankan wizardnya, dan dia akan segera mendeteksi segala perangkat jaringan yang kita miliki. Beri tahu Firestarter mana perangkat yang terhubung ke internet, dan mana perangkat yang terhubung ke jaringan lokal. Kemudian tentukan layanan atau port berapa yang ingin kita buka. Selesai sudah. Begitu mudah.

Selain Firestarter, kita juga bisa menggunakan GuardDog.

Sayangnya, kali ini saya ingin menginstal di server. Firestarter walaupun ampuh tapi kurang sesuai, karena dia berbasis GUI. Harus install Gnome dulu, kemudian kalau mau setting harus menggunakan X tunneling agar Firestarter bisa tampil di komputer yang me-remote.

Setelah tanya kanan kiri, Bos Ari memberikan petuah agar sebaiknya saya pakai Shorewall saja. Selain Shorewall, masih ada beberapa alternatif, seperti Arno’s Firewall yang direkomendasikan Anton. Tapi melihat sekilas dari tutorialnya, nampaknya Shorewall adalah yang paling intuitif untuk digunakan.

Baiklah, mari kita install dan konfigurasi Shorewall. Sebagai gambaran, komputer yang ingin saya bentengi cuma memiliki satu kartu jaringan yang langsung terhubung ke internet. Saya ingin membuka layanan SSH di port 22 dan HTTP di port 80.

Langkah pertama tentunya adalah menginstal Shorewall. Di Ubuntu tidak sulit, langsung saja

sudo apt-get install shorewall 

Shorewall akan segera terinstal, tapi tidak aktif. Kita harus mengkonfigurasi dulu, baru kemudian mengaktifkannya.

Konfigurasi Shorewall ada di folder /etc/shorewall.

Setelah Shorewall terinstal, pertama kita tentukan dulu jaringan yang terhubung ke komputer kita. Untuk kasus saya tidak sulit, cuma ada satu jaringan terhubung ke eth0.

Jaringan komputer, dalam dunia Shorewall disebut dengan istilah zone. Konfigurasinya ditulis di file bernama zones. Karena kita cuma punya satu jaringan, yaitu internet, berikut adalah isi file tersebut.

fw	firewall
net	ipv4

Shorewall ingin menaruh dirinya sendiri ke dalam zone terpisah. Oleh karena itu kita punya dua zone di file tersebut, yaitu net dan fw. ipv4 adalah jenis zone. Ada tiga jenis zone dalam shorewall, yaitu ipv4, firewall, dan ipsec. Bila kita menggunakan koneksi terenkripsi, kita bisa menggunakan opsi ipsec.

Zone dan device dihubungkan di file yang namanya interfaces. Isinya adalah sebagai berikut

net     eth0            detect          tcpflags,logmartians,nosmurfs,norfc1918

Kolom pertama menyatakan bahwa zone net dilayani oleh kartu jaringan eth0 yang disebutkan di kolom kedua. Selain kartu jaringan, kita juga bisa mendaftarkan perangkat lain seperti modem.

Kolom ketiga adalah konfigurasi broadcast. Satu perangkat jaringan bisa saja memiliki banyak IP address, atau terhubung dengan koneksi peer to peer. Dengan demikian satu perangkat bisa memiliki banyak alamat broadcast. Untuk memudahkan konfigurasi, kita bisa suruh Shorewall mencari alamat broadcast yang sesuai. Inilah arti dari konfigurasi detect.

Kolom terakhir paling kanan memuat opsi. Banyak opsi yang disediakan, saya cuma jelaskan opsi yang digunakan di atas saja.

  • tcpflags : ini memeriksa paket-paket yang memiliki kombinasi flags tidak lazim.
  • logmartians : ini artinya kita akan mencatat paket yang alamat asalnya aneh.
  • nosmurfs : ini mengatasi paket yang alamat asalnya sama dengan alamat broadcast.
  • norfc1918 : ini artinya kita akan mengabaikan semua paket dari dan menuju alamat private yang disebutkan di RFC 1918. Contoh alamat private antara lain adalah 192.168.0.1

Setelah selesai dengan zone dan interface, kita menentukan kebijakan global atau policy. Kebijakan ini berlaku apabila tidak ada aturan yang spesifik terhadap suatu paket. Biasanya, kebijakan global yang kita gunakan adalah:

  • mesin kita ini boleh menghubungi siapa saja. Semua paket keluar diijinkan (ACCEPT)
  • paket yang berasal dari luar menuju firewall akan diabaikan (DROP)
  • paket yang berasal dari luar menuju komputer dibalik firewall akan diabaikan (DROP)
  • selain itu, tolak semua (REJECT)

Konfigurasi kebijakan di atas ditulis di file bernama policy. Berikut isinya

#SOURCE		DEST		POLICY		LOG LEVEL	LIMIT:BURST
$FW		net		ACCEPT
net		$FW		DROP		info
net		all		DROP		info
# The FOLLOWING POLICY MUST BE LAST
all		all		REJECT		info
#LAST LINE -- ADD YOUR ENTRIES ABOVE THIS LINE -- DO NOT REMOVE

Terakhir, baru kita buat pengecualian terhadap kebijakan di atas. Seperti disebutkan di awal artikel, saya ingin membuka akses untuk web server di port 80 dan ssh server di port 22. Sebagai tambahan pengamanan untuk mencegah flooding, kita juga akan menolak paket ping yang masuk. Tapi kita ingin tetap bisa ping keluar.

Shorewall sudah punya konfigurasi standar (disebut dengan istilah macro) untuk aplikasi server yang umum digunakan. Kita tulis aturan ini di file rules. Berikut isinya

Ping/REJECT	net		$FW

# Permit all ICMP traffic FROM the firewall TO the net zone
ACCEPT		$FW		net		icmp

# Open SSH Service
SSH/ACCEPT	net		$FW

# Open Web Server
Web/ACCEPT	net		$FW

Ping/REJECT, SSH/ACCEPT dan Web/ACCEPT di atas adalah macro. Sebetulnya kita juga bisa membuka akses webserver di port 80 tanpa macro dengan konfigurasi seperti ini

ACCEPT    net       $FW             tcp  80

Tapi biasanya ada beberapa layanan yang membuka beberapa port sekaligus, misalnya FTP. Daripada harus menghafalkan port berapa saja yang harus dibuka untuk FTP, akan lebih mudah kalau kita gunakan macro FTP/ACCEPT

Selanjutnya, kita edit file shorewall.conf. Ini adalah konfigurasi global. Isi konfigurasi yang disediakan Ubuntu Gutsy sudah cukup bagus. Saya cuma mengubah baris ini

STARTUP_ENABLED=No

menjadi

STARTUP_ENABLED=Yes

Selain itu, juga ada satu file lagi, yaitu /etc/default/shorewall. Ganti baris

startup=0

menjadi

startup=1

Semuanya sudah siap. Silahkan nyalakan firewall Anda.

sudo /etc/init.d/shorewall start

Oh iya, berhati-hatilah kalau menginstal shorewall secara remote. Salah konfigurasi bisa menyebabkan kita terkunci di luar.


Aplikasi Web dengan Spring 2.5 [bagian 3]

Kemarin kita sudah membuat aplikasi sederhana yang memiliki tampilan list dan form. Tapi ada satu hal penting yang belum ada, yaitu header, footer, dan menu navigasi.

Ada beberapa cara untuk memasang header dan footer tanpa duplikasi kode. Cara paling sederhana tentunya dengan menggunakan include di setiap halaman. Contohnya kira-kira seperti ini.

personlist.html

#parse("header.html")


<table border="0" cellpadding="2" cellspacing="2">
	<tr>
		<th>Name</th>
		<th>Email</th>
		<th> </th>
	</tr>
	#foreach($person in $personList)
	<tr>
		<td>$person.Name</td>
		<td>$person.Email</td>
		<td><a href="personform?person_id=$person.Id">edit</a> | <a
			href="persondetail?person_id=$person.Id">view</a></td>
	</tr>
	#end
</table>


#parse("footer.html")

header.html

berisi kira-kira seperti ini

<html>
  <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>:: List of All Person ::</title>
  </head>

  <body>

dan

footer.html

</body>
</html>

Saya sendiri kurang suka dengan pendekatan seperti ini, karena:

  • kita terpaksa membuat halaman html yang tidak lengkap. Di header.html tidak ada tutup body dan di footer.html tidak ada tag pembuka body.
  • perintah include untuk header dan footer akan diulangi di setiap halaman.

Ada cara yang lebih baik, yaitu menggunakan decorator.

Pustaka decorator di Java cukup banyak, antara lain:

  • SiteMesh
  • Tiles
  • Facelets

Tiles tadinya adalah decorator khusus untuk framework Struts. Tapi seiring dengan populernya penggunaan decorator, maka Tiles direfactor sehingga dapat berdiri sendiri tanpa Struts. Saat artikel ini ditulis, baik Tiles maupun penerusnya Tiles 2 cuma bisa digunakan untuk JSP. Jadi tidak bisa kita terapkan untuk aplikasi kita yang menggunakan Velocity.

Facelets bukan hanya sekedar decorator. Dia adalah teknologi templating untuk JSF. Jadi posisinya kurang lebih mirip dengan Velocity, yaitu template engine. Decorator hanyalah salah satu fitur tambahan Facelets. Sayangnya Facelets hanya bisa digunakan untuk JSF.

Sitemesh adalah decorator independen. Tidak terikat dengan framework apa-apa. Dia juga mendukung JSP, Freemarker, dan Velocity. Kita akan menggunakan SiteMesh pada artikel ini.

SiteMesh bekerja secara transparan. Pada saat membuat halaman aplikasi, kita bisa langsung coding seperti biasa. Tidak ada perubahan kode HTML di dalam template Velocity. Ketika dideploy, SiteMesh akan mencegat semua respon yang dikirim appserver ke browser dan menambahkan header dan footer.

Sebagai contoh,

personlist.html

<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>:: List of All Person ::</title>
</head>
<body>

<table border="0" cellpadding="2" cellspacing="2">
	<tr>
		<th>Name</th>
		<th>Email</th>
		<th> </th>
	</tr>
	#foreach($person in $personList)
	<tr>
		<td>$person.Name</td>
		<td>$person.Email</td>
		<td><a href="personform?person_id=$person.Id">edit</a> | <a
			href="persondetail?person_id=$person.Id">view</a></td>
	</tr>
	#end
</table>


</body>
</html>

Seperti kita lihat, tidak ada kode khusus. Kodenya sama persis dengan artikel sebelumnya.

Ini adalah decoratornya, kita beri nama main.html.

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

<link href="${base}/css/main.css" rel="stylesheet" type="text/css" />
<link href="${base}/css/layout.css" rel="stylesheet" type="text/css" />

<title>${title}</title>
</head>
<body>

<div id="header">
<h1>Hello Spring 2.5</h1>
<h2>Tutorial Spring 2.5 Annotation Configuration</h2>
</div>

<div id="top-bar">
<ul>
	<li>hola, endymuhardin</li>
	<li><a href="#">help</a></li>
	<li><a href="#">logout</a></li>
</ul>
</div>

<div id="menu-bar">
<ul>
	<li><a href="${base}/tutorial/personlist">manage person</a></li>
	<li><a href="#">generate report</a></li>
	<li><a href="#">prevent global warming</a></li>
</ul>
</div>

<div id="main">

<div id="sidebar">

<h1>Manage Person</h1>
<ul>
	<li><a href="${base}/tutorial/personform">add person</a></li>
	<li><a href="${base}/tutorial/personlist">list of person</a></li>
</ul>

</div>

<div id="content">

${body}

</div>


</div>


<div id="footer">

<div id="copyright">Copyright &copy; 2008 :: ArtiVisi Intermedia
::</div>

<div id="company-logo">Company Logo</div>

</div>
</body>
</html>

Ada beberapa tag khusus yang digunakan dalam decorator kita, yaitu

  • ${base}
  • ${title}
  • ${body}

Ini adalah variabel yang disediakan SiteMesh untuk kita. ${base} adalah path menuju aplikasi web kita. Kita menggunakan ${base} untuk membuat URL yang lengkap. Variabel ini sangat berguna agar aplikasi kita bisa dideploy ke berbagai context.

Pada saat mendekorasi, SiteMesh akan membaca respon HTML yang dikeluarkan appserver dan mengambil isi tag title dan body, kemudian memasangnya di tempat kita menaruh tag ${title} dan ${body}. Setelah itu, barulah keseluruhan respon yang sudah didekorasi akan dikirim ke client.

Kita sudah punya halaman yang akan ditampilkan (personlist.html) dan decoratornya (main.html). Untuk menggabungkan keduanya, kita harus memasang SiteMesh sebagai Servlet Filter. Servlet Filter memiliki kemampuan untuk mencegat baik request maupun response. Kita mendaftarkan filter di web.xml. Berikut deklarasi filter SiteMesh berikut mappingnya.

    <filter>
      <filter-name>sitemesh</filter-name>
      <filter-class>com.opensymphony.module.sitemesh.filter.PageFilter</filter-class>
    </filter>	
    
    
    <filter-mapping>
      <filter-name>sitemesh</filter-name>
      <url-pattern>/*</url-pattern>
    </filter-mapping>

Selain itu, kita juga perlu memasang servlet SiteMesh. Servlet ini yang akan mencegat semua pemanggilan template velocity, yaitu semua file yang berakhiran *.html. Berikut adalah deklarasi servlet berikut mappingnya.

    
    <servlet>
      <servlet-name>sitemesh-velocity</servlet-name>
      <servlet-class>com.opensymphony.module.sitemesh.velocity.VelocityDecoratorServlet</servlet-class>
    </servlet>
    
    <servlet-mapping>
      <servlet-name>sitemesh-velocity</servlet-name>
      <url-pattern>*.html</url-pattern>
    </servlet-mapping>
    

Selanjutnya, kita harus memberi tahu SiteMesh di mana kita meletakkan decorator dan URL apa saja yang ingin didekorasi. Biasanya kita punya beberapa decorator yang berbeda untuk (misalnya):

  • halaman utama. Lengkap dengan menubar, header, dan footer
  • halaman login. Tanpa menubar, header, dan footer
  • tampilan siap print. Tanpa warna-warni.

Sebagai contoh, kita cuma akan menggunakan satu decorator, yaitu main.html. Decorator ini berlaku untuk semua request. Berikut konfigurasinya.

    <decorators defaultdir="/WEB-INF/decorators">
      <decorator name="default" page="main.html">
        <pattern>/*</pattern>
      </decorator>
    </decorators>

File ini diletakkan di dalam folder WEB-INF, bersama-sama dengan web.xml.

Pada konfigurasi decorator tersebut, kita jelaskan bahwa semua request (/*) akan dicegat dan didekorasi dengan main.html yang ada di folder /WEB-INF/decorators.

Terakhir, kita lengkapi pustaka yang dibutuhkan. Jar yang dibutuhkan adalah:

  • sitemesh.jar
  • commons-digester.jar
  • velocity-tools-view.jar

Perhatikan bahwa velocity-tools-view.jar ini sudah mengandung velocity-tools-generic.jar yang kita gunakan pada artikel sebelumnya. Jadi pastikan bahwa kita tidak lupa membuang velocity-tools-generic.jar.

Selesai sudah. Mudah bukan?

Contoh ini sudah dipublish di GoogleCode, Anda bisa mengunduh:

Atau Anda juga bisa menggunakan Subversion untuk menarik keseluruhan project folder, dengan perintah:

git clone https://github.com/endymuhardin/hello-spring-25.git

Setelah project folder diperoleh, dibutuhkan database MySQL dengan konfigurasi sebagai berikut:

  • nama database : belajar
  • username : belajar
  • password : java

Tabel T_PERSON yang digunakan pada contoh ini dapat dibuat dengan menggunakan script sql yang ada di folder src/sql/mysql-schema.sql.

Untuk menjalankan aplikasi ini, dibutuhkan Ant 1.7.0 dan Java 6. Cukup ketik ant run di konsol. Kemudian browse ke http://localhost:8080/

Selamat mencoba


Mengaktifkan commit email Subversion

Salah satu best practices dalam praktek pemrograman adalah Peer Review. Kode yang disubmit seseorang ke version control akan direview oleh anggota tim yang lain. Beberapa keuntungan dari pelaksanaan Peer Review antara lain:

  • meningkatnya collective code ownership. Semua anggota tim akan merasa memiliki.
  • membantu penyebaran pengetahuan dan pengalaman. Kode yang kurang optimal akan dikomentari di ruang publik (milis developer), sehingga semua anggota akan mendapat pencerahan.
  • meningkatkan kualitas kode program secara keseluruhan. Bila setiap commit direview, anggota tim akan mendapat peer pressure untuk tidak mengulangi kesalahan yang pernah dibahas. Kalau ada kode yang kurang optimal, feedback akan datang dengan cepat, sehingga kualitas yang rendah tidak akan terakumulasi.

Baiklah, sekarang kita sudah tahu manfaat praktek peer review. Sama seperti kita tahu kalau lari pagi itu sehat. Tapi seperti halnya lari pagi, banyak orang yang tidak melakukannya.

Salah satu alasan peer review tidak dilakukan adalah semakin banyak kode program, semakin sulit reviewnya. Mau mulai dari mana? Apa kode yang kemarin sudah direview perlu dilihat lagi?

Masalah yang sama dihadapi oleh seluruh pengembang aplikasi open source di seluruh dunia. Dan mereka sudah punya solusinya, yaitu commit email.

Prinsipnya sederhana. Kita pasang trigger di version control. Setiap kali ada yang commit, trigger tersebut akan melihat apa yang baru saja dicommit. Kemudian membandingkannya dengan versi sebelumnya. Hasil perbandingan kemudian dikirim ke milis developer supaya bisa dilihat orang banyak.

Dengan commit email ini, reviewer tidak perlu melihat keseluruhan kode program, tapi cukup yang berubah saja. Ini akan sangat meringankan kegiatan review.

Subversion sudah mempaketkan script trigger untuk commit email. Di halaman Tools & Contrib ada beberapa script untuk keperluan ini.

Saya menggunakan commit script yang dibuat dengan Perl. Sebelum dipakai, script ini harus diedit sedikit, di bagian SMTP server.

$smtp_server = "127.0.0.1";

dan bagian path menuju perintah svnlook.

my $svnlook = "@SVN_BINDIR@/svnlook";

Kalau di Windows, svnlook ada di C:\Program Files\Subversion\bin, sedangkan kalau di Linux biasanya ada di /usr/bin.

Selanjutnya, kita harus menyuruh Subversion untuk menjalankan file ini pada saat ada orang commit. Caranya mudah, masuk ke folder repository Subversion, di dalam folder hooks.

Untuk Linux mudah, cukup rename post-commit.tmpl menjadi post-commit, dan ganti modenya menjadi executable.

chmod +x post-commit

Kemudian edit sedikit untuk memasukkan beberapa parameter:

  • Recipient : penerima commit email. Biasanya ini saya arahkan ke milis developer
  • Subject : subject pada email yang dikirim
  • Reply To : kalau penerima email menekan tombol Reply, inilah alamat yang akan muncul di field To
  • Host : nama host otomatis yang akan ditambahkan ke nama user yang commit. Misalnya username yang commit endy, dan hostnya artivisi.com, maka nama pengirim otomatis menjadi endy@artivisi.com

Berikut isi dari file post-commit saya

#!/bin/sh
REPOS="$1"
REV="$2"

RECIPIENT=devteam@artivisi.com
SUBJECT=automated-commit-email
REPLY_TO=devteam@artivisi.com
HOST=artivisi.com

$REPOS/hooks/commit-email.pl "$REPOS" "$REV" -h $HOST -r $REPLY_TO  -s $SUBJECT $RECIPIENT

Untuk di Windows, kita harus membuat file post-commit.bat, di folder hooks juga. Berikut isi dari post-commit.bat

@ECHO OFF

SET RECIPIENT=devteam@artivisi.com
SET SUBJECT=automated-commit-email
SET REPLY_TO=devteam@artivisi.com
SET HOST=artivisi.com

SET PERL=c:\usr\bin\perl
SET REPOS=%1
SET REV=%2

%PERL% -w %REPOS%/hooks/commit-email.pl %REPOS% %REV% -h %HOST% -r %REPLY_TO%  -s %SUBJECT% %RECIPIENT%

Tentunya kita harus menginstal dulu Perl untuk Windows agar script ini bisa berjalan.

Selamat mencoba


Annotation dan XML

Salah satu komentator bertanya seperti ini,

kenapa annotation lebih diprefer daripada xml configuration ? bukannya xml configuration di spring membuat kontrol kita lebih sentralisasi, sehingga lebih mudah dimaintain ?

Hmm… saya tidak ingin terlibat flame war annotation vs xml. Masing-masing memiliki plus minusnya. Ada saatnya kita memakai annotation dan ada saatnya XML lebih tepat. Mari kita bahas.

Beberapa waktu yang lalu, saya pernah ditugaskan untuk memeriksa aplikasi finansial yang error. Pada salah satu halaman, bila tombol Submit ditekan, aplikasi akan hang. Aplikasi ini dibuat menggunakan teknologi VB 6 (RIP) untuk komponen akses database, ASP untuk tampilan, dan Stored Procedure MS-SQL Server 2005 untuk sebagian logika bisnis.

Setelah memasukkan trace statement ke dalam Stored Procedure (ternyata bukan hal yang mudah, tidak semudah menyisipkan System.out.println(), terlihat penyebabnya karena ada cyclic dependency antar transaksi database. Transaksi A mencoba mengurangi saldo, tapi sebelumnya memeriksa posisi saldo, kalau-kalau tidak mencukupi. Pemeriksaan saldo dilakukan melalui transaksi B. Transaksi B mencoba melihat tabel saldo, yang ternyata sudah dikunci oleh transaksi A. Ya sampai kapanpun transaksi A dan B akan saling menunggu.

Baiklah, masalah sudah ditemukan, saatnya untuk memperbaiki. Memperbaiki jauh lebih mudah daripada menemukan bug, kan?

Sayangnya tidak demikian. Ada berbagai faktor, salah satunya yang paling signifikan adalah saya awam dengan teknologi Microsoft. Saya sama sekali tidak mengerti bagaimana cara mengatur transaksi di VB dan Stored Procedure.

Akhirnya setelah google kesana kemari, saya menemukan empat cara untuk melakukan setting transaksi:

  • coding di VB
  • coding di Stored Procedure
  • konfigurasi class properties di VB
  • konfigurasi di COM service pada Control Panel Windows

Waduh … ini mimpi buruk. Berarti ada banyak kombinasi yang harus dicoba. Saya yakin para MVP pasti dengan cepat bisa memilih, tapi saya hanyalah seorang programmer Java. Mana saya tahu hal beginian.

Dari kejadian ini, saya mendapat pelajaran penting. Pengaturan transaksi haruslah dekat dengan kode yang melakukan transaksi. Alasannya, kode program yang mengakses database sangat erat hubungannya dengan pengaturan transaksi. Tidak mungkin kita menulis kode untuk transfer antar rekening tapi menon-aktifkan transaksi database.

Kode seperti ini mudah dipahami dan mudah didebug.

@Transactional(readOnly=false)
public void transferAntarRekening(Rekening asal, Rekening tujuan, BigDecimal jumlah){
  asal.credit(jumlah);
  tujuan.debet(jumlah);
}

Kode seperti ini perlu banyak Alt-Tab untuk mendebugnya.

Di Java:

public void transferAntarRekening(Rekening asal, Rekening tujuan, BigDecimal jumlah){
  asal.credit(jumlah);
  tujuan.debet(jumlah);
}

Di XML

<property name="transactionAttributes">
  <props>
    <prop key="transfer*">PROPAGATION_REQUIRED</prop>
  </props>
</property>

Bedakan antara cara melakukan transaksi, dan cara melakukan konfigurasi. Di Java, kita bisa memilih beberapa cara transaksi:

  • Local Transaction: mengelola transaksi melalui java.sql.Connection dalam source code
  • Programmatic Transaction: mengambil object Transaction dari JTS provider, kemudian menggunakannya untuk commit/rollback. Ini juga dilakukan dalam source code.
  • Declarative Transaction: mengatur transaksi melalui konfigurasi.

Kalau kita pilih declarative transaction, ada beberapa cara konfigurasinya:

  • Melalui XML
  • Melalui Annotation

Yang mana yang paling dekat dengan source code yang bertransaksi? Tentu saja annotation.

Ok, sudah jelas mengenai transaction, sebaiknya di annotation saja.

Berikutnya, deklarasi DAO. Sebelumnya kita melakukan deklarasi DAO di XML. Yang biasa saya lakukan kalau ada DAO baru biasanya sama:

  • copy paste deklarasi DAO di atasnya
  • ganti bean id
  • ganti nama class

Beres. Tidak ada added value di sini. Deklarasi manual di XML tidak membuat konfigurasi Spring jadi lebih mudah dipahami. Juga tidak membuat kita makin pintar. Ini adalah overhead pemrograman dengan Spring. Boilerplate code. Kode yang ditulis hanya untuk memuaskan framework.

Jadi, lebih baik kalau kita suruh Spring autodetect saja dari annotation @Repository.

Bagaimana dengan relasi dengan datasource? Bukankah kalau di XML jadi terlihat DAO mana menggunakan DataSource mana?

Hmm … untuk kasus datasource, ada dua kemungkinan:

  • menggunakan satu datasource
  • menggunakan lebih dari satu datasource

Untuk kasus pertama, biasanya sebagian aplikasi seperti ini, tidak perlu ada deklarasi eksplisit untuk injeksi dataSource. Lha wong cuma satu, apanya yang ambigu? @Autowired saja semuanya.

Untuk kasus kedua, mau tidak mau memang harus XML. Soalnya @Autowired by type tidak akan bisa, karena ada lebih dari satu bean bertipe DataSource.

Sekarang kita lihat, apa yang tertinggal di konfigurasi XML, setelah banyak hal kita pindahkan ke annotation:

  • deklarasi datasource
  • deklarasi transaction manager

Kedua hal ini tidak logis bila kita pindahkan ke annotation. Dua-duanya adalah konfigurasi, sangat mungkin berubah tergantung strategi deployment. Bila kita mengelola koneksi database melalui application server, maka kita akan menggunakan JNDI. Bila kita deploy di Winstone, maka kita tidak menggunakan JTA, jadi transaction manager menggunakan LocalTransactionManager.

Setelah selesai dengan isu backend, mari sekarang kita bahas presentation layer. Ada beberapa anotasi di sini:

  • @Controller : untuk menandai class Controller
  • @RequestMapping : untuk mapping URL atau Request Method ke handlernya
  • @RequestParam : untuk mem-bind request parameter ke variabel
  • @ModelAttribute : untuk mem-bind object ke request attribute
  • @SessionAttribute : untuk mem-bind object ke session attribute

@Controller sama dengan @Repository. Keberadaannya menghilangkan boilerplate deklarasi di XML. Menurut saya, ini sangat mengurangi clutter dan duplikasi di XML.

@RequestMapping, saya 50:50 di sini.

Di satu sisi, seharusnya kita tidak mengikat handler method dengan URL pattern, supaya kita bisa mengganti skema URL sesuai trend terbaru (RESTful, etc) atau sebaliknya, supaya bisa mengganti handler method tanpa mengubah URL.

Tapi di sisi lain, saya ingin menggunakan Convention over Configuration ala Rails. Yaitu nama view otomatis diambil dari request URL. Ini benar-benar mengurangi jumlah konfigurasi yang harus ditulis. Lagipula, seberapa sering sih kita mengganti pasangan URL-Handler?

Jadi untuk @RequestMapping, posisi saya adalah, tergantung situasi. Ada saatnya mapping di XML, dan ada saatnya mapping dengan annotation.

@RequestParam, @ModelAttribute, dan @SessionAttribute setahu saya tidak punya padanan XML. Ini adalah anotasi untuk memudahkan coding saja. Jadi tidak perlu diperdebatkan apakah sebaiknya di XML saja.

Yah, begitulah sekilas perbandingan antara konfigurasi melalui anotasi dan XML. Sepertinya menambah bingung. Tapi jangan khawatir, untuk bisa mengerti harus bingung dulu.

Semoga bermanfaat.

:D


Aplikasi Web dengan Spring 2.5 [bagian 2]

Pada artikel Spring bagian ketiga ini, kita akan membuat form untuk mengedit data Person. Di sini kita akan lihat kemampuan form binding dari Spring, cara menyuplai data ke form, melakukan validasi, dan memproses form ketika tombol Submit ditekan.

Kita akan menggunakan template yang sama untuk pengeditan Person yang sudah ada maupun pendaftaran Person baru. Templatenya bernama personform.html. Berikut kodenya.

personform.html

<html>

<head>
<title>:: Edit Person ::</title>
</head>

<body>
<form method="POST">
<input type="hidden" name="id" value="$!person.Id">

<table>
  <tr>
    <td>Nama</td>
    <td><input type="text" name="name" value="$!person.Name"></td>
  </tr>
  <tr>
    <td>Email</td>
    <td><input type="text" name="email" value="$!person.Email"></td>
  </tr>
  <tr>
    <td colspan="2"><input type="submit" value="Save"></td>
  </tr>
</table>

</form>
</body>

</html>

Kita melihat ada variabel yang agak berbeda pada contoh di atas, yaitu $!person. Ini merupakan variabel opsional dalam Velocity. Bila variabel $person tidak ada isinya, Velocity akan menampilkan apa adanya, yaitu $person ke halaman web. Kita ingin bila $person null, jangan tampilkan apa-apa. Untuk itu, kita mengubah variabel $person menjadi $!person.

Kalau dijadikan kode Java, kira-kira $!person sama dengan ini:

    String personName;
    if(person != null && person.getName() != null) {
      personName = person.getName();
    } else {
      personName = "";
    }

Variabel $!person ini digunakan karena form ini menangani New Person dan juga Edit Person. Untuk kasus Edit Person, kita dapat memberikan object person yang sudah ada di database. Sedangkan untuk New Person, objectnya belum ada atau null. Dengan $!person, kita dapat menangani kedua skenario ini.

Berikut kerangka class PersonFormController.

    package tutorial.spring25.ui.springmvc;
    
    @Controller
    @RequestMapping("/personform")
    public class PersonFormController {	
      private PersonDao personDao;
    
      @Autowired
      public void setPersonDao(final PersonDao personDao) {
        this.personDao = personDao;
      }
    
      @RequestMapping(method = RequestMethod.GET)
      public ModelMap displayForm(@RequestParam(value = "person_id", required = false) Long id) {
      
      }
    
      @RequestMapping(method = RequestMethod.POST)
      public String processForm(@ModelAttribute("person") Person person, BindingResult result, SessionStatus status) {
    
      }
    }

Ada dua method di sini, yaitu displayForm dan processForm. Yang satu untuk menampilkan form, dan satu lagi untuk memproses hasil submit. Nama method bebas saja, tidak ada aturan yang harus dipatuhi.

Kedua method dimapping ke request /personform. Dengan demikian, request ke http://localhost:8080/tutorial-spring25/tutorial/personform akan memanggil class PersonFormController. Di dalam form htmlnya juga action setelah submit dikosongkan. Artinya, kalau dia disubmit, form tersebut akan memanggil URL yang sama dengan yang memanggilnya.

Tetapi, bagaimana kita memilih kapan harus mendisplay form dan memproses form? Kita membedakannya dengan memasang annotation @RequestMapping dengan parameter RequestMethod. Bila requestnya GET (terjadi bila kita mengetik http://localhost:8080/tutorial-spring25/tutorial/personform di browser dan menekan Enter), maka jalankan method displayForm. Tapi bila requestnya POST (terjadi bila kita menekan tombol Submit di personform.html), maka jalankan method processForm.

Sekarang mari kita isi method displayForm. Berikut isinya

    @RequestMapping(method = RequestMethod.GET)
    public ModelMap displayForm(@RequestParam(value = "person_id", required = false) Long id) {
      Person person = personDao.getById(id);
    
      if (person == null) person = new Person();
    
      return new ModelMap(person);
    }

Di sini kita melakukan binding untuk request parameter person_id. Berbeda dengan tampilan detail pada artikel sebelumnya, di form ini parameter person_id belum tentu ada. Bila kita membuat object Person baru, field id akan berisi null. Untuk itu, kita berikan parameter required yang bernilai false pada anotasi @RequestMapping.

Logika pada method ini tidak rumit. Ambil object Person dari database berdasarkan id. Kalau tidak ada, berikan saja object baru.

Method ini bisa langsung dicoba dengan mengakses personform dengan memberikan parameter person_id, misalnya dengan URL http://localhost:8080/tutorial-spring25/tutorial/personform?person_id=100. Tentunya kita harus memiliki record di tabel T_PERSON dengan id 100. Kalau codingnya benar, maka akan tampil form yang terisi dengan data record tersebut.

Berikutnya, kita akan implementasi method untuk memproses form. Berikut isi method processForm

    @RequestMapping(method = RequestMethod.POST)
    public String processForm(@ModelAttribute("person") Person person) {
      personDao.save(person);
      return "redirect:personlist";
    }

Mudah kan? Cukup gunakan personDao untuk menyimpan object ke database, kemudian redirect ke halaman personlist.

Begitu saja? Tidak ada yang lupa?

Ya untuk memproses form begitu saja langkahnya, tidak perlu susah-susah.

Bagaimana dengan validasi? Mana ada form tanpa validasi.

Baiklah, mari kita tambahkan kode validasi. Untuk itu, method processForm perlu dimodifikasi menjadi seperti ini

    @RequestMapping(method = RequestMethod.POST)
    public String processForm(@ModelAttribute("person") Person person, BindingResult result, SessionStatus status) {
      new PersonValidator().validate(person, result);
      if (result.hasErrors()) {
        return "personform";
      } else {
        personDao.save(person);
        status.setComplete();
        return "redirect:personlist";
      }
    }

Tidak terlalu rumit, kan? Cukup buat class PersonValidator, kemudian jalankan method validate dengan input object person yang ingin divalidasi, dan object result untuk menampung error validasi bila ada.

Selanjutnya, kita periksa object result. Bila ada errornya, kembali ke form. Bila tidak ada, langsung save dengan personDao, set status menjadi complete, dan redirect ke personlist.

Isi class PersonValidator juga tidak banyak. Berikut kodenya.

    package tutorial.spring25.validator;
    public class PersonValidator {
    
      private static final String EMAIL_FORMAT = ".*@.*\\.com";
    
      public void validate(Person person, Errors errors) {
        // field nama harus diisi
        if(!StringUtils.hasText(person.getName())) {
          errors.rejectValue("name", "required", "nama harus diisi");
        }
    
        // bila field email diisi, formatnya harus benar
        if (StringUtils.hasLength(person.getEmail()) && !person.getEmail().matches(EMAIL_FORMAT) ) {
          errors.rejectValue("email", "email.format", "format email salah");
        }
      }
    }

Mudah bukan?

Para penggemar framework berbasis komponen (seperti Tapestry atau JSF) mungkin bertanya, untuk apa saya belajar lagi Spring MVC? Sepertinya tidak lebih mudah.

Coba perhatikan URL yang kita gunakan:

  • http://localhost:8080/tutorial-spring25/tutorial/personlist

  • http://localhost:8080/tutorial-spring25/tutorial/persondetail?person_id=100

  • http://localhost:8080/tutorial-spring25/tutorial/personform

  • http://localhost:8080/tutorial-spring25/tutorial/personform?person_id=100

Semuanya bersih dan bookmarkable. Dengan Spring MVC kita bisa mengatur URL sesuai keinginan.

Selanjutnya, coba perhatikan kode Java kita. Jangankan lokasi template Velocity kita, bahkan dia tidak tahu menahu kalau kita pakai Velocity. Tugas kode Java cuma menerima input dan mengembalikan data. Terserah data itu mau diformat seperti apa. Dia tidak peduli teknologi view yang digunakan.

Implikasinya, selama data yang disuplai tidak berubah, hanya dengan mengubah konfigurasi kita dapat mengubah tampilan. Tentunya kita harus menyediakan template yang sesuai.

Kita bisa membuat template dengan teknologi yang lain, misalnya JSP, Freemarker, atau Jasper Report. Kita juga bisa merender tampilan tidak hanya dalam format HTML, tapi juga PDF, XLS, XML, JSON, plain-text, atau mengkonversinya menjadi grafik SVG.

Kelebihan lainnya, kita mengendalikan secara penuh output HTML aplikasi kita. Implikasinya, kita bisa menerapkan teknik-teknik teruji dalam protokol HTTP seperti Cache Control pada HTTP Header.

Atau kita bisa manfaatkan HTTP Response Code 304 Not Modified untuk memberi tahu client bahwa halaman yang dia akses belum berubah sejak terakhir diakses, sehingga client tidak mendownload lagi keseluruhan page, melainkan langsung menampilkan local cache-nya.

Teknik seperti ini sederhana, mudah, sudah teruji di lapangan, berlaku untuk berbagai bahasa pemrograman, dan sangat efektif. Hanya dengan mengubah HTTP response code, kita bisa menghemat bandwidth dan mengurangi load application server. Sayangnya teknik ini belum tentu dapat digunakan pada framework yang terlalu canggih. Spring MVC memungkinkan kita untuk memanipulasi HTTP response dengan mudah kalau kita mau.

Source code untuk rangkaian artikel ini sudah dipublish di GoogleCode. Anda bisa:

Demikian sekilas tentang framework Spring MVC. Semoga bermanfaat.