22 Jun 2009
Menggunakan PostgreSQL Sequence dengan Hibernate
Pada artikel ini, kita akan membahas tentang bagaimana membuat Hibernate menggunakan sequence yang dibuat PostgreSQL.
Bila kita membuat Hibernate mapping untuk entity class sebagai berikut:
@Entity @Table(name="example")
public class Example {
@Id @GeneratedValue
private Integer id;
private String name;
// getter dan setter
}
kemudian menyuruh Hibernate untuk menggenerate DDL ke PostgreSQL, maka kita akan mendapatkan hasil sebagai berikut.
create table example (id int4 not null, name varchar(255), primary key (id))
create sequence example_id_seq
Artinya, Hibernate akan membuat sequence bernama example_id_seq dan menggunakannya untuk menghasilkan id.
Skema yang dihasilkan ini berbeda dengan skema yang biasa digunakan DBA PostgreSQL dalam membuat tabel, yaitu seperti ini
CREATE TABLE example (id serial, name text)
Bila kita menggunakan mapping di atas ke skema tabel dengan id bertipe serial ini, kita akan mendapatkan exception sebagai berikut.
SEVERE: ERROR: relation "hibernate_sequence" does not exist
Exception in thread "main" org.hibernate.exception.SQLGrammarException: could not get next sequence value
Caused by: org.postgresql.util.PSQLException: ERROR: relation "hibernate_sequence" does not exist
Ini disebabkan karena mapping di atas akan mencari sequence bernama hibernate_sequence yang tidak ada kalau kita membuat tabel dengan id serial.
Solusinya adalah dengan menggunakan Hibernate Annotation Extension, yaitu anotasi @GenericGenerator seperti ini.
@Entity @Table(name="example")
public class Example {
@Id @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="pg_seq")
@GenericGenerator(name="pg_seq", strategy="sequence", parameters={
@Parameter(name="sequence", value="example_id_seq")
})
private Integer id;
private String name;
}
Nama sequence diambil dari sequence yang dibuatkan PostgreSQL pada saat kita melakukan create table.
Nama sequence ini bisa dilihat dengan mengetikkan \d example, yang akan menghasilkan output sebagai berikut.
\d example
Table "public.example"
Column | Type | Modifiers
--------+---------+-------------------------------------------------------
id | integer | not null default nextval('example_id_seq'::regclass)
name | text |
Barulah setelah itu kita bisa menyimpan object ke database dengan mulus.
16 Jun 2009
Pada artikel sebelumnya, kita telah membahas tentang konsep integrasi aplikasi, dan implementasinya menggunakan Spring Integration. Contoh implementasi kita kemarin, walaupun mencakup integrasi end-to-end, masih sangat sederhana.
Kali ini kita akan mengeksplorasi use-case yang lebih kompleks, yaitu cara menangani berbagai operasi service. Misalnya untuk tipe data yang sama, contohnya Penjualan, kita memiliki service untuk simpan dan hapus. Object penjualan juga lebih kompleks daripada object produk.
Untuk memecahkan masalah ini, kita menggunakan Router. Router bertugas memilih channel sesuai dengan message yang datang. Berikut skema penggunaan router.
Sebelum membahas tentang router, baiklah kita lihat dulu data penjualan yang akan dikirim. Berikut class penjualan
Penjualan
public class Penjualan {
private Integer id;
private Date tanggal = new Date();
private List<PenjualanDetail> details = new ArrayList<PenjualanDetail>();
// getter dan setter
}
Berikutnya, penjualan detail.
PenjualanDetail
public class PenjualanDetail {
private Integer id;
private Penjualan penjualan;
private Produk produk;
private Integer jumlah;
// getter dan setter
}
Object baru ini tentu saja harus kita buatkan transformernya untuk mengkonversi object menjadi JSON.
CabangService
public class JsonTransformer{
public Penjualan jsonToPenjualan(String json){
Map<String , Class> binding = new HashMap<String , Class>();
binding.put("details", PenjualanDetail.class);
return (Penjualan) JSONObject.toBean(JSONObject.fromObject(json), Penjualan.class, binding);
}
public String penjualanToJson(Penjualan p){
// remove dulu cyclic dependency
for (PenjualanDetail detail : p.getDetails()) {
detail.setPenjualan(null);
}
return JSONObject.fromObject(p).toString();
}
}
Penjualan dan PenjualanDetail akan kita proses melalui method simpan dan hapus yang ada di CabangService.
CabangService
public class CabangService {
public void save(Penjualan p) {
System.out.println("Simpan penjualan dengan ID : "+p.getId());
System.out.println("Tanggal : "+new SimpleDateFormat("dd MMM yyyy").format(p.getTanggal()));
System.out.println("Details : ");
for (PenjualanDetail detail : p.getDetails()) {
System.out.println("Produk : "+detail.getProduk().getKode());
System.out.println("Jumlah : "+detail.getJumlah());
}
}
public void delete(Penjualan p) {
System.out.println("Hapus penjualan dengan ID : "+p.getId());
}
}
Tentunya nanti method save dan delete ini akan lebih canggih dari ini, misalnya insert dan delete ke database.
Kalau kita lihat skemanya, ada satu titik di mana message akan dilihat dan disalurkan ke channel yang sesuai.
Penjualan yang akan disimpan dimasukkan ke channel penjualan-simpan. Sedangkan object penjualan yang akan dihapus dimasukkan ke channel penjualan-hapus. Dengan demikian, kita harus membuat router yang mampu menentukan channel yang akan dipilih dengan melihat message yang masuk ke dalam router tersebut. Operasi yang akan dilakukan (simpan atau hapus) harus dimasukkan ke message header atau message content. Agar sederhana, kita tambahkan saja satu property di class Penjualan untuk menentukan operasi yang akan dilakukan. Bila class Penjualan ini dimapping menggunakan JPA, kita bisa menandai property ini dengan anotasi @Transient agar isinya tidak disimpan di database.
Karena nantinya property operasi ini akan digunakan tidak saja untuk Penjualan, tapi juga transaksi lainnya, baiklah kita buat saja di superclass DomainObject sebagai berikut.
Penjualan
public class DomainObject {
private String operasi;
}
Berikut class Penjualan yang sudah dimodifikasi.
Penjualan
public class Penjualan extends DomainObject {
private Integer id;
private Date tanggal = new Date();
private List<PenjualanDetail> details = new ArrayList< <PenjualanDetail>();
// getter dan setter
}
Property operasi ini akan dilihat oleh router untuk menentukan nama channel. Berikut implementasi Router.
Router
public class NamaKelasDanOperasiRouter {
public String pilihChannel(Object msg){
String classname = msg.getClass().getSimpleName().toLowerCase();
if(!DomainObject.class.isAssignableFrom(msg.getClass())) {
throw new IllegalArgumentException("Harus object bertipe : "+DomainObject.class.getName());
}
DomainObject obj = (DomainObject) msg;
String operasi = obj.getOperasi().toLowerCase();
return classname + "-" + operasi;
}
}
Router ini akan membaca nama class dari object yang diterimanya dan menggabungkannya dengan property operasi. Jadi, object Penjualan dengan operasi simpan akan menghasilkan nama channel penjualan-simpan. Demikian juga object Pembelian dengan operasi hapus akan menghasilkan channel pembelian-hapus.
Berikut unit test untuk router di atas.
Router
public class NamaKelasDanOperasiRouterTest {
@Test
public void testPilihChannel() {
Penjualan p = new Penjualan();
p.setOperasi("simpan");
assertEquals("penjualan-simpan", new NamaKelasDanOperasiRouter().pilihChannel(p));
}
}
Berikut aliran message mulai dari gateway sampai menjadi JSON.
Dan ini adalah konfigurasi Spring Integration untuk flow di atas.
Gateway ke JSON
<gateway id="gateway"
service-interface="com.artivisi.explore.spring.integration.gateway.Gateway"
default-request-channel="outgoingPenjualan"
/>
<channel id="outgoingPenjualan"/>
<transformer
input-channel="outgoingPenjualan"
output-channel="outgoingJson"
ref="jsonTransformer"
method="penjualanToJson"
/>
<channel id="outgoingJson"/>
Pada artikel sebelumnya, kita mengganti implementasi email dengan shared folder supaya proses development lebih cepat. Akses ke shared folder jauh lebih cepat daripada email yang dibatasi oleh kecepatan internet. Kali ini, kita akan menggunakan bridge, yaitu hubungan langsung antar channel. Ini kita gunakan untuk menghubungkan channel outgoingJson dengan incomingJson. Hubungan ini pada artikel sebelumnya diimplementasikan menggunakan shared folder dan email.
Berikut konfigurasi bridge untuk menghubungkan outgoingJson dan incomingJson.
Bridge
<bridge
input-channel="outgoingJson"
output-channel="incomingJson"
/>
Dari incomingJson, kita konversi dulu menjadi object.
Berikut konfigurasinya.
JSON ke Object
<channel id="incomingJson"/>
<transformer
input-channel="incomingJson"
output-channel="incomingPenjualan"
ref="jsonTransformer"
method="jsonToPenjualan"
/>
Setelah menjadi object, kita masukkan ke router untuk ditentukan channelnya.
Routing
<router
input-channel="incomingPenjualan"
ref="namaKelasDanOperasiRouter"
method="pilihChannel"
/>
Kemudian, dari channel kita sambungkan ke method save dan delete.
Method Save
<channel id="penjualan-simpan"/>
<service-activator
input-channel="penjualan-simpan"
ref="cabangService"
method="save"/>
Method Delete
<channel id="penjualan-hapus"/>
<service-activator
input-channel="penjualan-hapus"
ref="cabangService"
method="delete"/>
Terakhir, kita buat class untuk menjalankan semua rangkaian integrasi ini.
CabangRouterDemo
public class CabangRouterDemo {
public static void main(String[] args) {
// 1. Menginstankan Spring Application Context
AbstractApplicationContext ctx
= new ClassPathXmlApplicationContext("cabang-router-ctx.xml", CabangRouterDemo.class);
ctx.registerShutdownHook();
Gateway gw = (Gateway) ctx.getBean("gateway");
Penjualan p1 = bikinTransaksi(100, "simpan");
Penjualan p2 = bikinTransaksi(102, "hapus");
gw.send(p1);
gw.send(p2);
System.exit(0);
}
private static Penjualan bikinTransaksi(Integer id, String operasi) {
Penjualan p = new Penjualan();
p.setId(id);
p.setOperasi(operasi);
Produk pr1 = new Produk(1001, "PR-001", "Produk 001");
Produk pr2 = new Produk(1002, "PR-002", "Produk 002");
Produk pr3 = new Produk(1003, "PR-003", "Produk 003");
PenjualanDetail pd1 = new PenjualanDetail(11, pr1, 1);
PenjualanDetail pd2 = new PenjualanDetail(12, pr2, 2);
PenjualanDetail pd3 = new PenjualanDetail(13, pr3, 3);
p.addPenjualanDetail(pd1);
p.addPenjualanDetail(pd2);
p.addPenjualanDetail(pd3);
return p;
}
}
Demikianlah penggunaan routing dengan Spring Integration. Seperti kita lihat, dengan menggunakan Spring Integration, aplikasi kita menjadi fleksibel dan mudah ditest. Semua implementasi kode program Java, baik itu transformer, router, dan service method, bisa ditest dengan mudah menggunakan JUnit. Kita juga lihat bahwa semua kode program Java kita tidak memiliki ketergantungan terhadap Spring Integration.
15 Jun 2009
Di salah satu proyek ArtiVisi, kita memiliki aplikasi client-server yang terhubung melalui koneksi internet berbandwidth kecil. Client menghubungi server melalui modem GPRS.
Permasalahannya adalah bagaimana cara mengetes koneksi lemot ini pada saat development? Umumnya pada saat development kita menjalankan server dan client di komputer yang sama. Atau kalaupun di komputer berbeda, dijalankan di satu LAN, sehingga kecepatan bisa mencapai 100Mbps.
Ada dua alternatif yang bisa kita gunakan. Yang pertama adalah aplikasi tc (traffic control) yang biasanya sudah terinstal secara default di Ubuntu. Kalau belum terinstal, kita bisa menginstal paket iproute atau iproute2, tergantung distro yang Anda gunakan.
Alternatif kedua, menggunakan aplikasi ip_relay. Aplikasi ini bisa diinstal dengan nama paket iprelay.
Berikut adalah referensi pemakaian tc :
Dan ini adalah referensi pemakaian iprelay :
12 Jun 2009
Pada artikel sebelumnya, kita sudah mendiskusikan requirement yang diinginkan. Sekarang kita akan melakukan implementasi menggunakan Spring Integration.
Spring Integration memiliki beberapa abstraksi utama yang perlu kita ketahui agar bisa membuat implementasi, yaitu :
-
Message : ini adalah data yang akan kita kirim, proses, dan terima
-
Channel : ini adalah saluran tempat lewatnya message
-
Endpoint : ini adalah ujung dari channel
-
Transport : mekanisme pengiriman message. Spring Integration mendukung messaging (email,JMS), remoting (Web Service, RMI, HttpInvoker, HTTP), file, dan lainnya (stream)
Ilustrasi hubungan antara message, channel, dan endpoint bisa dilihat di gambar berikut :
Message terdiri dari dua bagian utama, yaitu header dan payload (isi message).
Channel
Berdasarkan kemampuan menampung message, channel dibedakan menjadi :
-
Pollable : memiliki buffer untuk menampung message. Dengan adanya kapasitas buffer, pengirim tidak perlu menunggu sampai penerima mendapatkan message, kecuali bila buffernya penuh. Dalam kondisi buffer penuh, pengirim akan menunggu (blocking) sampai ada slot kosong yang bisa digunakan untuk menyimpan message. Penerima dapat memeriksa keberadaan message dalam buffer. Dalam melakukan pemeriksaan, penerima dapat menunggu sampai ada message, atau sampai jangka waktu tertentu (timeout)
-
Subscribable : tidak memiliki buffer, tiap message yang masuk langsung dikirim ke endpoint. Pengirim harus menunggu sampai message diterima.
Berdasarkan perilaku pengiriman message, channel dibedakan menjadi :
-
Point to Point (PTP) : tiap message hanya dikirimkan ke satu penerima saja. Walaupun ada beberapa penerima yang terhubung ke channel PTP, tapi hanya salah satu saja yang akan menerima message
-
Point to Multipoint (PTM) : tiap message akan dikirim ke semua penerima yang terdaftar.
Ada beberapa implementasi channel :
-
Direct Channel : subscribable dan point-to-point. Seluruh proses (kirim, masuk channel, terima) akan dilakukan dalam thread yang sama
-
Queue Channel : pollable dan point to point. Message yang datang duluan akan dikirim duluan juga (FIFO)
-
Priority Channel : mirip dengan Queue Channel, tapi tidak menggunakan FIFO, melainkan melihat field Priority di Message Header untuk menentukan mana message yang harus dikirim terlebih dulu
-
Rendezvous Channel : mirip dengan Direct Channel, tapi pengirim dan penerima menggunakan thread yang berbeda. Pengirim akan menunggu sampai message diterima atau sampai timeout. Atau sebaliknya, penerima akan menunggu sampai ada message yang masuk. Biasanya digunakan untuk mengimplementasikan request-reply.
-
ThreadLocal Channel : pollable dan point-to-point. Message disimpan di thread local, sehingga tidak disharing dengan thread yang berbeda.
-
Publish Subscribe Channel : subscribable dan point-to-multipoint. Setiap penerima yang terdaftar akan menerima message. Tidak bisa menyimpan message, sehingga kalau kita butuh buffer, kita harus merangkainya dengan channel jenis lain yang memiliki buffer.
Endpoint
Endpoint digunakan untuk menghubungkan channel. Pemrosesan message dilakukan dalam endpoint. Ada beberapa jenis endpoint, yaitu:
-
Service Activator : kalau kita ingin memanggil business method kita, gunakan endpoint ini.
-
Channel Adapter : ini adalah penghubung channel dengan transport, baik untuk menerima message (inbound) ataupun mengirim message (outbound).
-
Transformer : digunakan untuk mengubah format message
-
Filter : digunakan untuk memutuskan apakah suatu message akan diterima atau dibuang
-
Router : digunakan untuk memilih channel mana yang akan menerima message
-
Splitter : digunakan untuk memecah message menjadi beberapa bagian untuk diproses secara independen
-
Aggregator : digunakan untuk menggabungkan beberapa message menjadi satu message untuk diteruskan ke channel berikutnya
-
Resequencer : digunakan untuk menyusun urutan message
Implementasi Kantor Pusat
Sekarang, setelah kita memahami berbagai istilah dalam Spring Integration, kita bisa merancang implementasi dari requirement kita. Berikut adalah aliran message di sisi kantor pusat. Kantor pusat mengirim beberapa data produk baru, dan akan dikonversi menjadi JSON.
Setelah menjadi JSON, selanjutnya kita bisa kirim melalui berbagai transport yang disediakan. Untuk tahap development, kita kirim saja melalui file ke folder /tmp, supaya mudah didebug dan tidak butuh internet.
Selanjutnya, setelah transport melalui file sudah dipastikan benar, baik format data, isi data, maupun rangkaian filternya, kita bisa mengganti channel adapter untuk mengirim ke GMail.
Kita harus membuat beberapa file sebagai berikut :
-
Produk.java : domain model dari data yang akan kita kirim
-
JsonTransformer.java : kode program untuk mengubah object Produk menjadi JSON
-
JsonTransformerTest.java : kode program untuk mengetes ProdukTransformer
-
PusatSender.java : kode program untuk mengaktifkan Spring Integration dan mengirim data produk
-
Gateway.java : interface untuk mengirim message. Kita harus membuat ini agar tidak ada dependensi ke library Spring Integration
-
pusat-integration-ctx.xml : konfigurasi Spring Integration
Berikut kode programnya.
Produk.java
public class Produk implements Serializable {
private Integer id;
private String kode;
private String nama;
// getter dan setter generate dengan IDE
}
public class JsonTransformer {
public Produk jsonToProduk(String json){
return (Produk) JSONObject.toBean(JSONObject.fromObject(json), Produk.class);
}
public String produkToJson(Produk p){
return JSONObject.fromObject(p).toString();
}
}
public class JsonTransformerTest {
@Test
public void testJsonToProduk() {
Produk p = new JsonTransformer()
.jsonToProduk("{\"id\":99,\"kode\":\"T-001\",\"nama\":\"Produk Test\"}");
assertEquals(new Integer(99), p.getId());
assertEquals("T-001", p.getKode());
assertEquals("Produk Test", p.getNama());
}
@Test
public void testProdukToJson() {
Produk p = new Produk();
p.setId(99);
p.setKode("T-001");
p.setNama("Produk Test");
assertEquals("{\"id\":99,\"kode\":\"T-001\",\"nama\":\"Produk Test\"}",
new JsonTransformer().produkToJson(p));
}
}
PusatSender.java
public class PusatSender {
public static void main(String[] args) {
// 1. Menginstankan Spring Application Context
AbstractApplicationContext ctx
= new ClassPathXmlApplicationContext("pusat-integration-ctx.xml", Gateway.class);
ctx.registerShutdownHook();
Gateway gw = (Gateway) ctx.getBean("gateway");
// 2. Kirim produk ke gateway
int jumlahProduk = 5;
for (int i = 0; i < jumlahProduk; i++) {
Produk p = new Produk();
p.setId(i);
p.setKode("PRD-00"+i);
p.setNama("Produk "+i);
gw.send(p);
System.out.println("Kirim produk "+i);
}
System.out.println("Produk terkirim");
System.exit(0);
}
}
Gateway.java
public interface Gateway {
public void send(Produk p);
}
pusat-integration-ctx.xml
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:file="http://www.springframework.org/schema/integration/file"
xmlns:mail="http://www.springframework.org/schema/integration/mail"
xmlns:transformer="http://www.springframework.org/schema/integration/transformer"
xmlns:stream="http://www.springframework.org/schema/integration/stream"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
http://www.springframework.org/schema/integration/file
http://www.springframework.org/schema/integration/file/spring-integration-file-1.0.xsd
http://www.springframework.org/schema/integration/mail
http://www.springframework.org/schema/integration/mail/spring-integration-mail-1.0.xsd
http://www.springframework.org/schema/integration/stream
http://www.springframework.org/schema/integration/stream/spring-integration-stream-1.0.xsd
http://www.springframework.org/schema/integration/transformer
http://www.springframework.org/schema/integration/transformer/spring-integration-transformer-1.0.xsd">
<gateway id="gateway"
service-interface="com.artivisi.explore.spring.integration.pusat.Gateway"
default-request-channel="outgoingProduk"/>
<channel id="outgoingProduk" />
<transformer input-channel="outgoingProduk" output-channel="outgoingJson"
ref="jsonTransformer" method="produkToJson"/>
<publish-subscribe-channel id="outgoingJson" />
<file:outbound-channel-adapter channel="outgoingJson" directory="/tmp"/>
<mail:header-enricher
id="mailHeaderEnricher"
subject="Spring Integration Demo"
to="cabang@gmail.com"
from="pusat@gmail.com"
reply-to="pusat@gmail.com"
overwrite="false"
input-channel="outgoingJson"
output-channel="outgoingEmail"/>
<channel id="outgoingEmail" />
<mail:outbound-channel-adapter
mail-sender="mailSender"
channel="outgoingEmail"
/>
<beans:bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
<beans:property name="defaultEncoding" value="UTF-8"/>
<beans:property name="host" value="smtp.gmail.com"/>
<beans:property name="port" value="465"/>
<beans:property name="username" value="mygmailaccount"/>
<beans:property name="password" value="mygmailpassword"/>
<beans:property name="javaMailProperties">
<beans:value>
mail.debug=true
mail.smtp.starttls.enable=true
mail.smtp.auth=true
mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
mail.smtp.socketFactory.fallback=false
</beans:value>
</beans:property>
</beans:bean>
<beans:bean id="jsonTransformer" class="com.artivisi.explore.spring.integration.model.JsonTransformer"/>
</beans:beans>
Implementasi Kantor Cabang
Di sisi kantor cabang, berikut aliran message dari transport hingga menjadi JSON. Kita mulai dengan transport file.
Bila kita menggunakan email, berikut gambarnya
Setelah menjadi JSON, kita proses sampai ke CabangService
Kita harus membuat beberapa file sebagai berikut :
-
CabangReceiver.java : kode program untuk mengaktifkan Spring Integration
-
CabangService.java : kode program yang akan dipanggil setelah message diterima, dikonversi dari JSON menjadi Produk
-
cabang-integration-ctx.xml : konfigurasi Spring Integration
Kode programnya.
CabangReceiver.java
public class CabangReceiver {
public static void main(String[] args) {
AbstractApplicationContext ctx
= new ClassPathXmlApplicationContext("cabang-integration-ctx.xml", CabangReceiver.class);
ctx.registerShutdownHook();
}
}
CabangService.java
public class CabangService {
public void terimaProduk(Produk p){
// Tampilkan data produk, tapi bisa juga disimpan di database
System.out.println("Terima produk");
System.out.println("ID : "+p.getId());
System.out.println("Kode : "+p.getKode());
System.out.println("Nama : "+p.getNama());
}
}
cabang-integration-ctx.xml
<beans:beans xmlns="http://www.springframework.org/schema/integration"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:file="http://www.springframework.org/schema/integration/file"
xmlns:mail="http://www.springframework.org/schema/integration/mail"
xmlns:transformer="http://www.springframework.org/schema/integration/transformer"
xmlns:stream="http://www.springframework.org/schema/integration/stream"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/integration
http://www.springframework.org/schema/integration/spring-integration-1.0.xsd
http://www.springframework.org/schema/integration/stream
http://www.springframework.org/schema/integration/stream/spring-integration-stream-1.0.xsd
http://www.springframework.org/schema/integration/file
http://www.springframework.org/schema/integration/file/spring-integration-file-1.0.xsd
http://www.springframework.org/schema/integration/mail
http://www.springframework.org/schema/integration/mail/spring-integration-mail-1.0.xsd
http://www.springframework.org/schema/integration/transformer
http://www.springframework.org/schema/integration/transformer/spring-integration-transformer-1.0.xsd">
<file:inbound-channel-adapter id="filePoller"
directory="/tmp"
filename-pattern="^.*\.msg$"
channel="incomingFile"/>
<channel id="incomingFile" />
<file:file-to-string-transformer
input-channel="incomingFile"
output-channel="incomingJson"
delete-files="true" />
<channel id="incomingJson" />
<transformer input-channel="incomingJson" output-channel="incomingProduk"
ref="jsonTransformer" method="jsonToProduk"/>
<channel id="incomingProduk" />
<service-activator input-channel="incomingProduk"
ref="cabangService"
method="terimaProduk"/>
<poller id="defaultPoller" default="true">
<interval-trigger interval="3" time-unit="SECONDS"/>
</poller>
<beans:bean id="jsonTransformer" class="com.artivisi.explore.spring.integration.model.JsonTransformer"/>
<beans:bean id="cabangService" class="com.artivisi.explore.spring.integration.cabang.CabangService"/>
</beans:beans>
Untuk menerima email, kita dapat memilih protokol POP3, IMAP, atau IMAP-Idle. POP3 dan IMAP akan mendownload semua email yang masuk, setelah itu, kita harus melakukan polling dengan interval tertentu untuk memeriksa apakah ada email baru. Bila kita menggunakan IMAP-Idle, kita tidak perlu melakukan polling. Mail server akan memberikan notifikasi bila ada email baru yang masuk, setelah itu kita bisa mendownloadnya.
Berikut konfigurasi untuk GMail
Debug Output
<beans:bean id="javaMailProperty" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
<beans:property name="properties">
<beans:value>
mail.debug=true
</beans:value>
</beans:property>
</beans:bean>
POP3
<mail:inbound-channel-adapter
channel="incomingMail"
store-uri="pop3s://mygmailaccount:mygmailpassword@pop.gmail.com:995/INBOX"
java-mail-properties="javaMailProperty"/>
IMAP
<mail:inbound-channel-adapter
channel="incomingMail"
store-uri="imaps://mygmailaccount:mygmailpassword@imap.gmail.com:993/INBOX" />
IMAP-Idle
<mail:imap-idle-channel-adapter channel="incomingMail"
store-uri="imaps://mygmailaccount:mygmailpassword@imap.gmail.com:993/INBOX"/>
Setelah email masuk, jangan lupa konversi dulu jadi String.
<channel id="incomingMail" />
<mail:mail-to-string-transformer
input-channel="incomingMail"
output-channel="incomingJson"/>
Demikianlah tutorial penggunaan Spring Integration. Dengan menggunakan Spring Integration ini, ada beberapa benefit yang kita dapatkan dibandingkan coding sendiri :
-
Kode program lebih sedikit
-
Kode program lebih mudah ditest. Kita bisa menggunakan JUnit untuk JsonTransformer. Alur pengiriman data juga bisa ditest di komputer lokal tanpa koneksi ke GMail, sehingga kalau ada error bisa diperbaiki dengan lebih cepat.
-
Aliran data lebih mudah dibaca, yaitu dalam konfigurasi Spring Integration
-
Fleksibilitas dalam pemilihan transport. Bukan cuma email, tapi juga banyak opsi lain seperti remoting dan file. Opsi ini cuma butuh beberapa baris konfigurasi saja untuk mengaktifkannya.
Konsumsi effort :
-
Download : 1 jam
-
Browsing tutorial : 2 jam
-
Mempelajari Spring Integration : 8 jam
-
Membuat sample aplikasi : 4 jam
-
Menulis blog dan membuat gambar : 8 jam
-
Total : 23 jam (3 hari)
Apakah dalam 3 hari kita bisa membuat implementasi kirim-terima data produk via email yang bebas bug ??
11 Jun 2009
Integrasi Aplikasi Pusat Cabang - Bagian I
Beberapa waktu yang lalu, kami di ArtiVisi dihadapkan pada suatu tantangan, yaitu sinkronisasi aplikasi antara pusat dan cabang.
Aplikasi pusat maupun cabang memiliki database masing-masing, dan tentunya data transaksi masing-masing pula.
Pada artikel ini, saya akan mengelaborasi proses problem solving yang dilakukan, solusi yang dipilih, dan tentunya dilengkapi dengan source code.
Use Case
Ada beberapa use case yang ingin didukung oleh aplikasi, sebagai berikut :
-
Pusat menambahkan jenis produk baru, data ini harus segera diimplementasikan juga di cabang
-
Transaksi yang dilakukan di cabang harus dikirim ke pusat agar bisa dibuatkan laporan keseluruhan
-
Data pelanggan di cabang harus juga dikirim ke pusat
-
Perpindahan stok barang dari pusat ke cabang dan sebaliknya
Constraint
Bicara requirement, tentu tidak bisa dilepaskan dari constraint. Berikut constraint yang ada :
-
Pusat dan cabang sama-sama terhubung ke internet menggunakan Speedy
-
Karena pakai Speedy, kita bisa mengekspose satu mesin ke internet menggunakan IP Public
-
IP Public tidak dedicated, dan mungkin berubah sewaktu-waktu
-
Server aplikasi dan database di pusat/cabang belum tentu hidup. Kalaupun hidup, belum tentu hidup berbarengan.
Bisa saja pusat sedang nyala, cabang mati lampu, dan sebaliknya.
Alternatif Solusi
Setelah kita memiliki requirement dan constraint, tahap berikutnya adalah membuat daftar alternatif solusi.
Berikut adalah alternatif yang bisa kita lakukan untuk memecahkan masalah di atas.
-
Remoting
-
Web Services
-
RMI
-
HttpInvoker
-
Messaging
-
File
-
Shared Folder
-
FTP
-
SSH/SCP
-
Database
-
Shared Database
-
Replikasi Database
Pemilihan Solusi
Jaringan publik melalui internet sangat tidak reliable dan memiliki banyak keterbatasan.
Kita tidak bisa membuka sembarang port, karena mungkin saja diblokir di tengah jalan.
Kita juga tidak bisa menggunakan protokol yang terlalu kompleks atau cerewet (membutuhkan transfer data yang sering).
Oleh karena itu, kita bisa menyingkirkan alternatif berikut.
-
Remoting dengan RMI : port tidak standar
-
Messaging dengan JMS : port tidak standar
-
Replikasi MySQL : protokol terlalu kompleks dan cerewet, port tidak standar
Kita harus mempertimbangkan konsumsi bandwidth. Protokol yang rakus bandwidth bisa kita singkirkan, yaitu Remoting dengan WS.
Melihat constraint terakhir, kita juga harus mengeliminasi beberapa alternatif yang mengharuskan pusat dan cabang online bersamaan, yaitu Remoting dengan Spring HTTPInvoker dan File Transfer, baik FTP maupun SSH/SCP.
Dengan demikian, yang tersisa adalah Messaging dengan Email. Kita bisa mengirim data dengan attachment. Untuk menghemat bandwidth, attachment bisa dikompresi. Pusat dan cabang bisa online kapanpun mereka mau, dan akan tetap menerima message. Kalau ada message yang hilang di tengah jalan, orang pusat/cabang bisa menelepon untuk minta dikirim ulang datanya.
Sebagai bonus, dengan memilih email sebagai media transfer data, kita tidak perlu repot-repot mengelola infrastruktur sendiri. Kita bisa gunakan layanan GMail yang gratis, berkapasitas besar, dan memiliki implementasi POP3, IMAP, dan SMTP. Ada satu fitur istimewa dari IMAP, yaitu IMAP-Idle. Fitur ini akan sangat berguna kelak di kemudian hari. Kebetulan GMail sudah mendukung fitur ini.
Desain Implementasi
Baiklah, mari kita implementasikan sinkronisasi melalui email. Ada beberapa pertanyaan lanjutan. Pertama, apa yang ingin dikirim? Sebagai contoh sederhana, kita ingin mengirim data produk baru dari pusat ke cabang. Inilah class Produk.
public class Produk {
private Integer id;
private String kode;
private String nama;
// getter dan setter
}
Bila kita membuat object Produk, datanya akan tersimpan di memori. Kita harus mengkonversi format data di memori ini ke bentuk file, supaya bisa dikirim dalam bentuk attachment. Ada beberapa pilihan lagi di sini, yaitu :
Serializable lebih unggul dalam hal kemudahan coding dan kecepatan marshall/unmarshall. Di Java menulis object ke file sangat mudah dan cepat. Prosesnya sama untuk apapun object yang ingin kita konversi. Untuk memproses konversi juga tidak dibutuhkan CPU dan Memori tinggi. Walaupun demikian, file yang dihasilkan berbentuk binary, sehingga menyulitkan debugging.
JSON dan XML berbentuk text, sehingga mudah didebug kalau ada masalah. Walaupun demikian, kita harus membuat atau memilih dari yang ada implementasi generator dan parser untuk proses konversi. Proses konversinya butuh resource CPU dan memori. Dan yang paling penting, kita mungkin harus membuat generator/parser untuk tiap tipe data yang akan kita kirim.
Sementara saya pilih JSON, karena ada json-lib yang bisa mengkonversi object menjadi JSON dan sebaliknya dengan mudah.
Selanjutnya, bagaimana cara mengirim email?
Cara mengirim email dengan Java sudah ditunjukkan Ifnu di sini. Tapi saya tidak mau coding tangan kosong seperti itu. Bukan apa-apa, setiap baris kode yang kita tulis harus ditest dan dimaintain. Belum lagi kalo nanti ada perubahan requirement, bisa coding ulang.
Nah, pertama saya memutuskan menggunakan Spring untuk mengirim email. Ibaratnya kalau coding sendiri itu berkelahi dengan tangan kosong, pakai Spring berkelahi pakai pistol. Jauh lebih cepat dan mudah.
Tapi ternyata, pistol juga tidak cukup, karena kita belum bisa menerima email. Bisa kirim gak bisa terima ya percuma, karena di sisi kantor cabang kita tentu harus mengambil dan memproses data yang dikirim dari pusat. Ifnu sebetulnya sudah membuat implementasi penerima emailnya, tapi nampaknya belum diposting.
Saatnya membuka gudang senjata dan mencari senjata yang lebih besar. Saya menemukan rocket launcher, yaitu Spring Integration.
Bukan saja bisa menerima email, Spring Integration juga bisa menggunakan semua alternatif transport yang disebutkan di atas (WS, HTTPInvoker, RMI, FTP, SSH, JMS, Email) hanya dengan mengganti beberapa baris konfigurasi. Wow …. canggih sekali. Baiklah, saya akan pakai ini saja.
Berhubung artikel ini sudah terlalu panjang. Implementasi Spring Integrationnya diteruskan di bagian kedua.